From 78a1406c33ed69af3cafae79f427167da8d84e8a Mon Sep 17 00:00:00 2001 From: vkumbhar94 Date: Fri, 15 Mar 2024 13:25:41 +0530 Subject: [PATCH] fix(stream): stream returning same type streams defined as methods --- slice.go | 23 ++++ slice_methods.go | 253 ++++++++++++++++++++++++++++++++++++++++++ slice_methods_test.go | 170 ++++++++++++++++++++++++++++ slice_test.go | 35 +++++- 4 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 slice_methods.go create mode 100644 slice_methods_test.go diff --git a/slice.go b/slice.go index 2487d40..e52d90c 100644 --- a/slice.go +++ b/slice.go @@ -167,6 +167,10 @@ func AllMatch[T any](s *Stream[T], f func(T) bool) bool { return true } +func NotAllMatch[T any](s *Stream[T], f func(T) bool) bool { + return !AllMatch(s, f) +} + func AnyMatch[T any](s *Stream[T], f func(T) bool) bool { s.Run() for t := range s.data { @@ -303,3 +307,22 @@ func Skip[T any](s *Stream[T], n int) *Stream[T] { }, } } + +func IfAllMatch[T any](s *Stream[T], f func(T) bool, action func(t T)) { + s.Run() + allMatch := true + var data []T + for t := range s.data { + if !f(t) { + allMatch = false + go drain(s.data) + break + } + data = append(data, t) + } + if allMatch { + for _, t := range data { + action(t) + } + } +} diff --git a/slice_methods.go b/slice_methods.go new file mode 100644 index 0000000..d164880 --- /dev/null +++ b/slice_methods.go @@ -0,0 +1,253 @@ +package streams + +import ( + "sync/atomic" +) + +func (s *Stream[T]) Filter(filter FilterFun[T]) *Stream[T] { + ch := make(chan T) + return &Stream[T]{ + data: ch, + run: func() { + s.Run() + defer close(ch) + for t := range s.data { + if filter(t) { + ch <- t + } + } + }, + } +} + +func (s *Stream[T]) Limit(i int) *Stream[T] { + ch := make(chan T) + return &Stream[T]{ + data: ch, + run: func() { + s.Run() + func() { + defer close(ch) + for t := range s.data { + if i > 0 { + ch <- t + i-- + } else { + break + } + } + }() + + for range s.data { + } + + }, + } +} + +func (s *Stream[T]) ForEach(f func(i T)) { + s.Run() + for t := range s.data { + f(t) + } +} + +func (s *Stream[T]) AllMatch(f func(T) bool) bool { + s.Run() + for t := range s.data { + if !f(t) { + go drain(s.data) + return false + } + } + return true +} + +func (s *Stream[T]) NotAllMatch(f func(T) bool) bool { + return !s.AllMatch(f) +} + +func (s *Stream[T]) AnyMatch(f func(T) bool) bool { + s.Run() + for t := range s.data { + if f(t) { + go drain(s.data) + return true + } + } + return false +} + +func (s *Stream[T]) NoneMatch(f func(T) bool) bool { + return !AnyMatch(s, f) +} + +func (s *Stream[T]) DropWhile(f func(T) bool) *Stream[T] { + ch := make(chan T) + return &Stream[T]{ + data: ch, + run: func() { + s.Run() + defer close(ch) + dropping := true + for t := range s.data { + if dropping && f(t) { + continue + } + dropping = false + ch <- t + } + }, + } +} + +func (s *Stream[T]) TakeWhile(f func(T) bool) *Stream[T] { + ch := make(chan T) + return &Stream[T]{ + data: ch, + run: func() { + s.Run() + func() { + defer close(ch) + for t := range s.data { + if f(t) { + ch <- t + } else { + break + } + } + }() + go drain(s.data) + }, + } +} + +func (s *Stream[T]) Peek(f func(T)) *Stream[T] { + ch := make(chan T) + return &Stream[T]{ + data: ch, + run: func() { + s.Run() + defer close(ch) + for t := range s.data { + f(t) + ch <- t + } + }, + } +} + +type OrStream[T any] struct { + Stream[T] +} + +func (s *OrStream[T]) Or(or T) T { + s.Run() + for t := range s.data { + go drain(s.data) + return t + } + return or +} + +func (s *Stream[T]) FindFirst() *T { + s.Run() + for t := range s.data { + go drain(s.data) + return &t + } + return nil +} + +func (s *Stream[T]) FindFirstOr() *OrStream[T] { + s.Run() + for t := range s.data { + go drain(s.data) + ch := make(chan T) + return &OrStream[T]{ + Stream[T]{ + data: ch, + run: func() { + defer close(ch) + ch <- t + }, + ran: atomic.Bool{}, + }, + } + } + ch := make(chan T) + return &OrStream[T]{ + Stream[T]{ + data: ch, + run: func() { + defer close(ch) + }, + ran: atomic.Bool{}, + }, + } +} + +func (s *Stream[T]) Skip(n int) *Stream[T] { + ch := make(chan T) + return &Stream[T]{ + data: ch, + run: func() { + s.Run() + defer close(ch) + for t := range s.data { + if n > 0 { + n-- + continue + } + ch <- t + } + }, + } +} + +type ElseStream[T any] struct { + Stream[T] +} + +func (s *ElseStream[T]) Else(action func(t T)) { + s.Run() + for t := range s.data { + action(t) + } +} + +func (s *Stream[T]) IfAllMatch(f func(T) bool, action func(t T)) *ElseStream[T] { + s.Run() + allMatch := true + var data []T + for t := range s.data { + if allMatch && !f(t) { + allMatch = false + } + data = append(data, t) + } + if allMatch { + for _, t := range data { + action(t) + } + } + ch := make(chan T) + return &ElseStream[T]{ + Stream: Stream[T]{ + data: ch, + run: func() { + defer close(ch) + for _, t := range data { + ch <- t + } + }, + ran: atomic.Bool{}, + }, + } +} + +func (s *Stream[T]) Collect() []T { + return Reduce(s, []T{}, func(ans []T, i T) []T { + return append(ans, i) + }) +} diff --git a/slice_methods_test.go b/slice_methods_test.go new file mode 100644 index 0000000..63b4e07 --- /dev/null +++ b/slice_methods_test.go @@ -0,0 +1,170 @@ +package streams + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFilter(t *testing.T) { + collected := New([]int{1, 2, 3, 4, 5}...).Filter(func(i int) bool { + return i > 2 + }).Collect() + if !reflect.DeepEqual(collected, []int{3, 4, 5}) { + t.Errorf("Expected [3, 4, 5] but got %v", collected) + } +} + +func TestLimit(t *testing.T) { + collected := New([]int{1, 2, 3, 4, 5}...).Limit(3).Collect() + if !reflect.DeepEqual(collected, []int{1, 2, 3}) { + t.Errorf("Expected [1, 2, 3] but got %v", collected) + } +} + +func TestForEach(t *testing.T) { + var sum int + New([]int{1, 2, 3, 4, 5}...).ForEach(func(i int) { + sum += i + }) + if sum != 15 { + t.Errorf("Expected 15 but got %v", sum) + } +} + +func TestMethodAllMatch(t *testing.T) { + allMatch := New([]int{1, 2, 3, 4, 5}...).AllMatch(func(i int) bool { + return i < 10 + }) + if !allMatch { + t.Errorf("Expected true but got %v", allMatch) + } +} + +func TestMethodFalseAllMatch(t *testing.T) { + allMatch := New([]int{1, 2, 3, 4, 5}...).AllMatch(func(i int) bool { + return i < 3 + }) + if allMatch { + t.Errorf("Expected false but got %v", allMatch) + } +} + +func TestMethodNotAllMatch(t *testing.T) { + allMatch := New([]int{1, 2, 3, 4, 5}...).NotAllMatch(func(i int) bool { + return i < 10 + }) + if allMatch { + t.Errorf("Expected false but got %v", allMatch) + } +} + +func TestMethodAnyMatch(t *testing.T) { + anyMatch := New([]int{1, 2, 3, 4, 5}...).AnyMatch(func(i int) bool { + return i == 3 + }) + if !anyMatch { + t.Errorf("Expected true but got %v", anyMatch) + } +} +func TestMethodFalseAnyMatch(t *testing.T) { + anyMatch := New([]int{1, 2, 3, 4, 5}...).AnyMatch(func(i int) bool { + return i == 10 + }) + if anyMatch { + t.Errorf("Expected false but got %v", anyMatch) + } +} + +func TestMethodNoneMatch(t *testing.T) { + noneMatch := New([]int{1, 2, 3, 4, 5}...).NoneMatch(func(i int) bool { + return i == 6 + }) + if !noneMatch { + t.Errorf("Expected true but got %v", noneMatch) + } +} + +func TestMethodDropWhile(t *testing.T) { + collected := New([]int{1, 2, 3, 4, 5}...).DropWhile(func(i int) bool { + return i < 3 + }).Collect() + if !reflect.DeepEqual(collected, []int{3, 4, 5}) { + t.Errorf("Expected [3, 4, 5] but got %v", collected) + } +} + +func TestMethodTakeWhile(t *testing.T) { + collected := New([]int{1, 2, 3, 4, 5}...).TakeWhile(func(i int) bool { + return i < 3 + }).Collect() + if !reflect.DeepEqual(collected, []int{1, 2}) { + t.Errorf("Expected [1, 2] but got %v", collected) + } +} + +func TestMethodPeek(t *testing.T) { + var sum int + collected := New([]int{1, 2, 3, 4, 5}...).Peek(func(i int) { + sum += i + }).Collect() + if !reflect.DeepEqual(collected, []int{1, 2, 3, 4, 5}) { + t.Errorf("Expected [1, 2, 3, 4, 5] but got %v", collected) + } + if sum != 15 { + t.Errorf("Expected 15 but got %v", sum) + } +} + +func TestMethodFindFirst(t *testing.T) { + first := New([]int{1, 2, 3, 4, 5}...).FindFirst() + if *first != 1 { + t.Errorf("Expected 1 but got %v", first) + } +} + +func TestMethodFindFirstNil(t *testing.T) { + first := New([]int{}...).FindFirst() + if first != nil { + t.Errorf("Expected nil but got %v", first) + } +} + +func TestMethodFindFirstOr(t *testing.T) { + first := New([]int{1, 2, 3}...).FindFirstOr().Or(10) + assert.Equal(t, 1, first) +} +func TestMethodFindFirstOrEmpty(t *testing.T) { + first := New([]int{}...).FindFirstOr().Or(2) + assert.Equal(t, 2, first) +} + +func TestMethodSkip(t *testing.T) { + collected := New([]int{1, 2, 3, 4, 5}...).Skip(3).Collect() + if !reflect.DeepEqual(collected, []int{4, 5}) { + t.Errorf("Expected [4, 5] but got %v", collected) + } +} + +func TestMethodIfAllMatch(t *testing.T) { + var sum int + New([]int{1, 2, 3, 4, 5}...).IfAllMatch(func(i int) bool { + return i < 10 + }, func(i int) { + sum += i * 3 + }) + assert.Equal(t, 45, sum) +} + +func TestMethodIfAllMatchElse(t *testing.T) { + var sum int + New([]int{1, 2, 3, 4, 5}...).IfAllMatch(func(i int) bool { + return i < 3 + }, func(i int) { + sum += i + }).Else(func(i int) { + sum += i * 2 + }) + assert.Equal(t, 30, sum) +} diff --git a/slice_test.go b/slice_test.go index 9ad9801..f8863d0 100644 --- a/slice_test.go +++ b/slice_test.go @@ -111,11 +111,18 @@ func TestAllMatch(t *testing.T) { }) assert.True(t, allMatch) } +func TestFalseAllMatch(t *testing.T) { + stream := New(1, 2, 3, 4, 5) + allMatch := AllMatch(stream, func(i int) bool { + return i < 3 + }) + assert.False(t, allMatch) +} func TestNotAllMatch(t *testing.T) { stream := New(1, 2, 3, 4, 5) - allMatch := AllMatch(stream, func(i int) bool { - return i < 5 + allMatch := NotAllMatch(stream, func(i int) bool { + return i < 10 }) assert.False(t, allMatch) } @@ -214,3 +221,27 @@ func TestSkip(t *testing.T) { collected := Collect(Skip(stream, 2)) assert.Equal(t, []int{3, 4, 5}, collected) } + +func TestIfAllMatch(t *testing.T) { + stream := New(1, 2, 3, 4, 5) + IfAllMatch(stream, + func(i int) bool { + return i < 10 + }, + func(t int) { + fmt.Println(t) + }, + ) +} + +func TestIfAllMatchNegativeCase(t *testing.T) { + stream := New(1, 2, 3, 4, 5) + IfAllMatch(stream, + func(i int) bool { + return i > 10 + }, + func(t int) { + fmt.Println(t) + }, + ) +}