diff --git a/stream/stream.go b/stream/stream.go index cdab636..464c8b0 100644 --- a/stream/stream.go +++ b/stream/stream.go @@ -1,9 +1,8 @@ // Copyright 2023 dudaodong@gmail.com. All rights resulterved. // Use of this source code is governed by MIT license -// Package stream implements a sequence of elements supporting sequential and parallel aggregate operations. -// this package is an experiment to explore if stream in go can work as the way java does. it's complete, but not -// powerful like other libs +// Package stream implements a sequence of elements supporting sequential and operations. +// this package is an experiment to explore if stream in go can work as the way java does. it's function is very limited. package stream import ( @@ -53,12 +52,13 @@ type stream[T any] struct { } // Of creates a stream stream whose elements are the specified values. +// Play: https://go.dev/play/p/jI6_iZZuVFE func Of[T any](elems ...T) stream[T] { return FromSlice(elems) } // Generate stream where each element is generated by the provided generater function -// generater function: func() func() (item T, ok bool) {} +// Play: https://go.dev/play/p/rkOWL1yA3j9 func Generate[T any](generator func() func() (item T, ok bool)) stream[T] { source := make([]T, 0) @@ -75,11 +75,13 @@ func Generate[T any](generator func() func() (item T, ok bool)) stream[T] { } // FromSlice creates stream from slice. +// Play: https://go.dev/play/p/wywTO0XZtI4 func FromSlice[T any](source []T) stream[T] { return stream[T]{source: source} } // FromChannel creates stream from channel. +// Play: https://go.dev/play/p/9TZYugGMhXZ func FromChannel[T any](source <-chan T) stream[T] { s := make([]T, 0) @@ -91,6 +93,7 @@ func FromChannel[T any](source <-chan T) stream[T] { } // FromRange creates a number stream from start to end. both start and end are included. [start, end] +// Play: https://go.dev/play/p/9Ex1-zcg-B- func FromRange[T constraints.Integer | constraints.Float](start, end, step T) stream[T] { if end < start { panic("stream.FromRange: param start should be before param end") @@ -109,6 +112,7 @@ func FromRange[T constraints.Integer | constraints.Float](start, end, step T) st } // Concat creates a lazily concatenated stream whose elements are all the elements of the first stream followed by all the elements of the second stream. +// Play: https://go.dev/play/p/HM4OlYk_OUC func Concat[T any](a, b stream[T]) stream[T] { source := make([]T, 0) @@ -119,6 +123,7 @@ func Concat[T any](a, b stream[T]) stream[T] { } // Distinct returns a stream that removes the duplicated items. +// Play: https://go.dev/play/p/eGkOSrm64cB func (s stream[T]) Distinct() stream[T] { source := make([]T, 0) @@ -147,6 +152,7 @@ func hashKey(data any) string { } // Filter returns a stream consisting of the elements of this stream that match the given predicate. +// Play: https://go.dev/play/p/MFlSANo-buc func (s stream[T]) Filter(predicate func(item T) bool) stream[T] { source := make([]T, 0) @@ -160,6 +166,7 @@ func (s stream[T]) Filter(predicate func(item T) bool) stream[T] { } // Map returns a stream consisting of the elements of this stream that apply the given function to elements of stream. +// Play: https://go.dev/play/p/OtNQUImdYko func (s stream[T]) Map(mapper func(item T) T) stream[T] { source := make([]T, s.Count()) @@ -171,6 +178,7 @@ func (s stream[T]) Map(mapper func(item T) T) stream[T] { } // Peek returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream. +// Play: https://go.dev/play/p/u1VNzHs6cb2 func (s stream[T]) Peek(consumer func(item T)) stream[T] { for _, v := range s.source { consumer(v) @@ -181,6 +189,7 @@ func (s stream[T]) Peek(consumer func(item T)) stream[T] { // Skip returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream. // If this stream contains fewer than n elements then an empty stream will be returned. +// Play: https://go.dev/play/p/fNdHbqjahum func (s stream[T]) Skip(n int) stream[T] { if n <= 0 { return s @@ -201,6 +210,7 @@ func (s stream[T]) Skip(n int) stream[T] { } // Limit returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length. +// Play: https://go.dev/play/p/qsO4aniDcGf func (s stream[T]) Limit(maxSize int) stream[T] { if s.source == nil { return s @@ -220,6 +230,7 @@ func (s stream[T]) Limit(maxSize int) stream[T] { } // AllMatch returns whether all elements of this stream match the provided predicate. +// Play: https://go.dev/play/p/V5TBpVRs-Cx func (s stream[T]) AllMatch(predicate func(item T) bool) bool { for _, v := range s.source { if !predicate(v) { @@ -231,6 +242,7 @@ func (s stream[T]) AllMatch(predicate func(item T) bool) bool { } // AnyMatch returns whether any elements of this stream match the provided predicate. +// Play: https://go.dev/play/p/PTCnWn4OxSn func (s stream[T]) AnyMatch(predicate func(item T) bool) bool { for _, v := range s.source { if predicate(v) { @@ -242,11 +254,13 @@ func (s stream[T]) AnyMatch(predicate func(item T) bool) bool { } // NoneMatch returns whether no elements of this stream match the provided predicate. +// Play: https://go.dev/play/p/iWS64pL1oo3 func (s stream[T]) NoneMatch(predicate func(item T) bool) bool { return !s.AnyMatch(predicate) } // ForEach performs an action for each element of this stream. +// Play: https://go.dev/play/p/Dsm0fPqcidk func (s stream[T]) ForEach(action func(item T)) { for _, v := range s.source { action(v) @@ -254,20 +268,23 @@ func (s stream[T]) ForEach(action func(item T)) { } // Reduce performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any. -func (s stream[T]) Reduce(init T, accumulator func(a, b T) T) T { +// Play: https://go.dev/play/p/g-xkHiuIpTi +func (s stream[T]) Reduce(initial T, accumulator func(a, b T) T) T { for _, v := range s.source { - init = accumulator(init, v) + initial = accumulator(initial, v) } - return init + return initial } // Count returns the count of elements in the stream. +// Play: https://go.dev/play/p/r3koY6y_Xo- func (s stream[T]) Count() int { return len(s.source) } // FindFirst returns the first element of this stream and true, or zero value and false if the stream is empty. +// Play: https://go.dev/play/p/9xEf0-6C1e3 func (s stream[T]) FindFirst() (T, bool) { var result T @@ -279,6 +296,7 @@ func (s stream[T]) FindFirst() (T, bool) { } // Reverse returns a stream whose elements are reverse order of given stream. +// Play: https://go.dev/play/p/A8_zkJnLHm4 func (s stream[T]) Reverse() stream[T] { l := len(s.source) source := make([]T, l) @@ -290,6 +308,7 @@ func (s stream[T]) Reverse() stream[T] { } // Range returns a stream whose elements are in the range from start(included) to end(excluded) original stream. +// Play: https://go.dev/play/p/indZY5V2f4j func (s stream[T]) Range(start, end int) stream[T] { if start < 0 { start = 0 @@ -315,6 +334,7 @@ func (s stream[T]) Range(start, end int) stream[T] { } // Sorted returns a stream consisting of the elements of this stream, sorted according to the provided less function. +// Play: https://go.dev/play/p/XXtng5uonFj func (s stream[T]) Sorted(less func(a, b T) bool) stream[T] { source := []T{} source = append(source, s.source...) @@ -326,6 +346,7 @@ func (s stream[T]) Sorted(less func(a, b T) bool) stream[T] { // Max returns the maximum element of this stream according to the provided less function. // less: a > b +// Play: https://go.dev/play/p/fm-1KOPtGzn func (s stream[T]) Max(less func(a, b T) bool) (T, bool) { var max T @@ -343,6 +364,7 @@ func (s stream[T]) Max(less func(a, b T) bool) (T, bool) { // Min returns the minimum element of this stream according to the provided less function. // less: a < b +// Play: https://go.dev/play/p/vZfIDgGNRe_0 func (s stream[T]) Min(less func(a, b T) bool) (T, bool) { var min T @@ -360,6 +382,7 @@ func (s stream[T]) Min(less func(a, b T) bool) (T, bool) { } // ToSlice return the elements in the stream. +// Play: https://go.dev/play/p/jI6_iZZuVFE func (s stream[T]) ToSlice() []T { return s.source } diff --git a/stream/stream_example_test.go b/stream/stream_example_test.go new file mode 100644 index 0000000..3dc8474 --- /dev/null +++ b/stream/stream_example_test.go @@ -0,0 +1,375 @@ +package stream + +import ( + "fmt" +) + +func ExampleOf() { + s := Of(1, 2, 3) + + data := s.ToSlice() + + fmt.Println(data) + + // Output: + // [1 2 3] +} + +func ExampleFromSlice() { + s := FromSlice([]int{1, 2, 3}) + + data := s.ToSlice() + + fmt.Println(data) + + // Output: + // [1 2 3] +} + +func ExampleFromChannel() { + ch := make(chan int) + go func() { + for i := 1; i < 4; i++ { + ch <- i + } + close(ch) + }() + + s := FromChannel(ch) + + data := s.ToSlice() + + fmt.Println(data) + + // Output: + // [1 2 3] +} + +func ExampleFromRange() { + s := FromRange(1, 5, 1) + + data := s.ToSlice() + fmt.Println(data) + + // Output: + // [1 2 3 4 5] +} + +func ExampleGenerate() { + n := 0 + max := 4 + + generator := func() func() (int, bool) { + return func() (int, bool) { + n++ + return n, n < max + } + } + + s := Generate(generator) + + data := s.ToSlice() + + fmt.Println(data) + + // Output: + // [1 2 3] +} + +func ExampleConcat() { + s1 := FromSlice([]int{1, 2, 3}) + s2 := FromSlice([]int{4, 5, 6}) + + s := Concat(s1, s2) + + data := s.ToSlice() + + fmt.Println(data) + + // Output: + // [1 2 3 4 5 6] +} + +func ExampleStream_Distinct() { + originalStream := FromSlice([]int{1, 2, 2, 3, 3, 3}) + distinctStream := originalStream.Distinct() + + data1 := originalStream.ToSlice() + data2 := distinctStream.ToSlice() + + fmt.Println(data1) + fmt.Println(data2) + + // Output: + // [1 2 2 3 3 3] + // [1 2 3] +} + +func ExampleStream_Filter() { + original := FromSlice([]int{1, 2, 3, 4, 5}) + + isEven := func(n int) bool { + return n%2 == 0 + } + + even := original.Filter(isEven) + + fmt.Println(even.ToSlice()) + + // Output: + // [2 4] +} + +func ExampleStream_Map() { + original := FromSlice([]int{1, 2, 3}) + + addOne := func(n int) int { + return n + 1 + } + + increament := original.Map(addOne) + + fmt.Println(increament.ToSlice()) + + // Output: + // [2 3 4] +} + +func ExampleStream_Peek() { + original := FromSlice([]int{1, 2, 3}) + + data := []string{} + peekStream := original.Peek(func(n int) { + data = append(data, fmt.Sprint("value", n)) + }) + + fmt.Println(original.ToSlice()) + fmt.Println(peekStream.ToSlice()) + fmt.Println(data) + + // Output: + // [1 2 3] + // [1 2 3] + // [value1 value2 value3] +} + +func ExampleStream_Skip() { + original := FromSlice([]int{1, 2, 3, 4}) + + s1 := original.Skip(-1) + s2 := original.Skip(0) + s3 := original.Skip(1) + s4 := original.Skip(5) + + fmt.Println(s1.ToSlice()) + fmt.Println(s2.ToSlice()) + fmt.Println(s3.ToSlice()) + fmt.Println(s4.ToSlice()) + + // Output: + // [1 2 3 4] + // [1 2 3 4] + // [2 3 4] + // [] +} + +func ExampleStream_Limit() { + original := FromSlice([]int{1, 2, 3, 4}) + + s1 := original.Limit(-1) + s2 := original.Limit(0) + s3 := original.Limit(1) + s4 := original.Limit(5) + + fmt.Println(s1.ToSlice()) + fmt.Println(s2.ToSlice()) + fmt.Println(s3.ToSlice()) + fmt.Println(s4.ToSlice()) + + // Output: + // [] + // [] + // [1] + // [1 2 3 4] +} + +func ExampleStream_AllMatch() { + original := FromSlice([]int{1, 2, 3}) + + result1 := original.AllMatch(func(item int) bool { + return item > 0 + }) + + result2 := original.AllMatch(func(item int) bool { + return item > 1 + }) + + fmt.Println(result1) + fmt.Println(result2) + + // Output: + // true + // false +} + +func ExampleStream_AnyMatch() { + original := FromSlice([]int{1, 2, 3}) + + result1 := original.AnyMatch(func(item int) bool { + return item > 1 + }) + + result2 := original.AnyMatch(func(item int) bool { + return item > 3 + }) + + fmt.Println(result1) + fmt.Println(result2) + + // Output: + // true + // false +} + +func ExampleStream_NoneMatch() { + original := FromSlice([]int{1, 2, 3}) + + result1 := original.NoneMatch(func(item int) bool { + return item > 3 + }) + + result2 := original.NoneMatch(func(item int) bool { + return item > 1 + }) + + fmt.Println(result1) + fmt.Println(result2) + + // Output: + // true + // false +} + +func ExampleStream_ForEach() { + original := FromSlice([]int{1, 2, 3}) + + result := 0 + original.ForEach(func(item int) { + result += item + }) + + fmt.Println(result) + + // Output: + // 6 +} + +func ExampleStream_Reduce() { + original := FromSlice([]int{1, 2, 3}) + + result := 0 + + original.ForEach(func(item int) { + result += item + }) + + fmt.Println(result) + + // Output: + // 6 +} + +func ExampleStream_FindFirst() { + original := FromSlice([]int{1, 2, 3}) + + result, ok := original.FindFirst() + + fmt.Println(result) + fmt.Println(ok) + + // Output: + // 1 + // true +} + +func ExampleStream_Reverse() { + original := FromSlice([]int{1, 2, 3}) + + reverse := original.Reverse() + + fmt.Println(reverse.ToSlice()) + + // Output: + // [3 2 1] +} + +func ExampleStream_Range() { + original := FromSlice([]int{1, 2, 3}) + + s1 := original.Range(0, 0) + s2 := original.Range(0, 1) + s3 := original.Range(0, 3) + s4 := original.Range(1, 2) + + fmt.Println(s1.ToSlice()) + fmt.Println(s2.ToSlice()) + fmt.Println(s3.ToSlice()) + fmt.Println(s4.ToSlice()) + + // Output: + // [] + // [1] + // [1 2 3] + // [2] +} + +func ExampleStream_Sorted() { + original := FromSlice([]int{4, 2, 1, 3}) + + sorted := original.Sorted(func(a, b int) bool { return a < b }) + + fmt.Println(original.ToSlice()) + fmt.Println(sorted.ToSlice()) + + // Output: + // [4 2 1 3] + // [1 2 3 4] +} + +func ExampleStream_Max() { + original := FromSlice([]int{4, 2, 1, 3}) + + max, ok := original.Max(func(a, b int) bool { return a > b }) + + fmt.Println(max) + fmt.Println(ok) + + // Output: + // 4 + // true +} + +func ExampleStream_Min() { + original := FromSlice([]int{4, 2, 1, 3}) + + min, ok := original.Min(func(a, b int) bool { return a < b }) + + fmt.Println(min) + fmt.Println(ok) + + // Output: + // 1 + // true +} + +func ExampleStream_Count() { + s1 := FromSlice([]int{1, 2, 3}) + s2 := FromSlice([]int{}) + + fmt.Println(s1.Count()) + fmt.Println(s2.Count()) + + // Output: + // 3 + // 0 +} diff --git a/stream/stream_test.go b/stream/stream_test.go index 076d005..6523cce 100644 --- a/stream/stream_test.go +++ b/stream/stream_test.go @@ -147,13 +147,17 @@ func TestStream_Peek(t *testing.T) { func TestStream_Skip(t *testing.T) { assert := internal.NewAssert(t, "TestStream_Peek") - stream := FromSlice([]int{1, 2, 3, 4, 5, 6}) + stream := FromSlice([]int{1, 2, 3, 4}) s1 := stream.Skip(-1) s2 := stream.Skip(0) + s3 := stream.Skip(1) + s4 := stream.Skip(2) - assert.Equal([]int{1, 2, 3, 4, 5, 6}, s1.ToSlice()) - assert.Equal([]int{1, 2, 3, 4, 5, 6}, s2.ToSlice()) + assert.Equal([]int{1, 2, 3, 4}, s1.ToSlice()) + assert.Equal([]int{1, 2, 3, 4}, s2.ToSlice()) + assert.Equal([]int{2, 3, 4}, s3.ToSlice()) + assert.Equal([]int{3, 4}, s4.ToSlice()) } func TestStream_Limit(t *testing.T) { @@ -248,6 +252,16 @@ func TestStream_Reduce(t *testing.T) { assert.Equal(6, result) } +func TestStream_Count(t *testing.T) { + assert := internal.NewAssert(t, "TestStream_Count") + + s1 := FromSlice([]int{1, 2, 3}) + s2 := FromSlice([]int{}) + + assert.Equal(3, s1.Count()) + assert.Equal(0, s2.Count()) +} + func TestStream_FindFirst(t *testing.T) { assert := internal.NewAssert(t, "TestStream_FindFirst")