Skip to content

Commit

Permalink
Merge pull request #69 from meian/67-empty-chaining
Browse files Browse the repository at this point in the history
#67 Empty chaining
  • Loading branch information
meian authored Jan 30, 2022
2 parents 456acc5 + 95b74be commit f87c1ae
Show file tree
Hide file tree
Showing 24 changed files with 180 additions and 56 deletions.
11 changes: 11 additions & 0 deletions common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,14 @@ func testBeforeAndAfter[T any](t *testing.T, itb gcf.Iterable[T]) {
assert.Zero(it.Current())
})
}

// testEmptyChain tests Iterable is emptyIterable when chained from emptyIterable.
// On function called with emptyIterable should be emptyIterable.
func testEmptyChain(t *testing.T, f func(itb gcf.Iterable[int]) gcf.Iterable[int]) {
t.Helper()
itbe := gcf.FromSlice([]int{})
t.Run("is empty Iterable", func(t *testing.T) {
itb := f(itbe)
assert.True(t, gcf.IsEmptyIterable(itb), "%v", gcf.ToSlice(itb))
})
}
6 changes: 3 additions & 3 deletions concat.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ type concatIterator[T any] struct {
// itb2 := gcf.FromSlice([]int{4, 5, 6})
// itbc := gcf.Concat(itb1, itb2)
func Concat[T any](itb1 Iterable[T], itb2 Iterable[T]) Iterable[T] {
if itb1 == nil && itb2 == nil {
if isEmpty(itb1) && isEmpty(itb2) {
return empty[T]()
}
if itb2 == nil {
if isEmpty(itb2) {
return itb1
}
if itb1 == nil {
if isEmpty(itb1) {
return itb2
}
return &concatIterable[T]{itb1, itb2}
Expand Down
7 changes: 7 additions & 0 deletions concat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ func TestConcat(t *testing.T) {
itb := gcf.FromSlice([]int{1, 2, 3})
itb = gcf.Concat(itb, gcf.FromSlice([]int{4, 5, 6}))
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.Concat(itb, gcf.FromSlice([]int{}))
})
testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.Concat(itb, nil)
})
}

func ExampleConcat() {
Expand Down
4 changes: 2 additions & 2 deletions distinct.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ type distinctIterator[T comparable] struct {
//
// Currently, result order is determined, but on spec, is undefined.
func Distinct[T comparable](itb Iterable[T]) Iterable[T] {
if itb == nil {
return empty[T]()
if isEmpty(itb) {
return orEmpty(itb)
}
// Because no change has with Distinct combined Distinct, original Iterable is returned
if itbd, ok := itb.(*distinctIterable[T]); ok {
Expand Down
4 changes: 4 additions & 0 deletions distinct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func TestDistinct(t *testing.T) {
itb := gcf.FromSlice([]int{1, 2, 3, 2, 4})
itb = gcf.Distinct(itb)
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.Distinct(itb)
})
}

func ExampleDistinct() {
Expand Down
15 changes: 15 additions & 0 deletions empty.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ func empty[T any]() Iterable[T] {
return emptyIterable[T]{}
}

func orEmpty[T any](itb Iterable[T]) Iterable[T] {
if itb == nil {
return empty[T]()
}
return itb
}

func isEmpty[T any](itb Iterable[T]) bool {
if itb == nil {
return true
}
_, ok := itb.(emptyIterable[T])
return ok
}

func (itb emptyIterable[T]) Iterator() Iterator[T] {
return emptyIter[T]()
}
Expand Down
6 changes: 6 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gcf

// IsEmptyIterable detects itb is emptyIterable.
func IsEmptyIterable[T any](itb Iterable[T]) bool {
return isEmpty(itb)
}
4 changes: 2 additions & 2 deletions filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ type filterIterator[T any] struct {
//
// If filterFunc is nil, returns original Iteratable.
func Filter[T any](itb Iterable[T], filterFunc func(v T) bool) Iterable[T] {
if itb == nil {
return empty[T]()
if isEmpty(itb) {
return orEmpty(itb)
}
if filterFunc == nil {
return itb
Expand Down
4 changes: 4 additions & 0 deletions filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func TestFilter(t *testing.T) {
itb := gcf.FromSlice([]int{1, 2, 3, 4})
itb = gcf.Filter(itb, func(v int) bool { return v%2 == 0 })
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.Filter(itb, func(v int) bool { return true })
})
}

func ExampleFilter() {
Expand Down
4 changes: 2 additions & 2 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type mapIterator[T any, R any] struct {
//
// If mapFunc is nil, return Iterable in zero value elements.
func Map[T any, R any](itb Iterable[T], mapFunc func(v T) R) Iterable[R] {
if itb == nil {
if isEmpty(itb) {
return empty[R]()
}
if mapFunc == nil {
Expand Down Expand Up @@ -69,7 +69,7 @@ type flatMapIterator[T any, R any] struct {
//
// If mapFunc is nil, return empty Iterable.
func FlatMap[T any, R any](itb Iterable[T], mapFunc func(v T) []R) Iterable[R] {
if itb == nil {
if isEmpty(itb) {
return empty[R]()
}
if mapFunc == nil {
Expand Down
12 changes: 10 additions & 2 deletions map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ func TestMap(t *testing.T) {
assert.Equal(tt.want, s)
})
}

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.Map(itb, func(v int) int { return v })
})
}

func ExampleMap() {
Expand Down Expand Up @@ -136,10 +140,14 @@ func TestFlatMap(t *testing.T) {
}

itb := gcf.FromSlice([]int{1, 2, 3, 4})
itbs := gcf.Map(itb, func(v int) string {
return strings.Repeat("a", v)
itbs := gcf.FlatMap(itb, func(v int) []string {
return []string{strings.Repeat("a", v)}
})
testBeforeAndAfter(t, itbs)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.FlatMap(itb, func(v int) []int { return []int{v, v, v} })
})
}

func ExampleFlatMap() {
Expand Down
6 changes: 6 additions & 0 deletions range.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ func Range[T constraints.Integer](start, end, step T) (Iterable[T], error) {
if start == end {
return FromSlice([]T{start}), nil
}
if step > 0 && start > end {
return empty[T](), nil
}
if step < 0 && start < end {
return empty[T](), nil
}
return &rangeIterable[T]{start, end, step}, nil
}

Expand Down
9 changes: 9 additions & 0 deletions range_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ func TestRange(t *testing.T) {
testBeforeAndAfter(t, itb)
itb, _ = gcf.Range(3, 1, -1)
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
itb, _ = gcf.Range(1, 0, 1)
return itb
})
testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
itb, _ = gcf.Range(1, 2, -1)
return itb
})
}

func ExampleRange() {
Expand Down
4 changes: 2 additions & 2 deletions repeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ type repeatIterableIterator[T any] struct {
//
// If count is 0 or negative, return Iterable with no element.
func RepeatIterable[T any](itb Iterable[T], count int) Iterable[T] {
if itb == nil {
return empty[T]()
if isEmpty(itb) {
return orEmpty(itb)
}
if count < 1 {
return empty[T]()
Expand Down
14 changes: 14 additions & 0 deletions repeat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ func TestRepeat(t *testing.T) {

itb := gcf.Repeat(1, 3)
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.Repeat(1, 0)
})
testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.Repeat(1, -1)
})
}

func ExampleRepeat() {
Expand Down Expand Up @@ -116,6 +123,13 @@ func TestRepeatIterable(t *testing.T) {
itb := gcf.FromSlice([]int{1, 2, 3})
itb = gcf.RepeatIterable(itb, 2)
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.RepeatIterable(itb, 0)
})
testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.RepeatIterable(itb, -1)
})
}

func ExampleRepeatIterable() {
Expand Down
4 changes: 2 additions & 2 deletions reverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ type reverseIterator[T any] struct {
// itb := gcf.FromSlice([]int{1, 2, 3})
// itb = gcf.Reverse(itb)
func Reverse[T any](itb Iterable[T]) Iterable[T] {
if itb == nil {
return empty[T]()
if isEmpty(itb) {
return orEmpty(itb)
}
// reverse in reverse is original Iterable.
if itbr, ok := itb.(*reverseIterable[T]); ok {
Expand Down
4 changes: 4 additions & 0 deletions reverse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func TestReverse(t *testing.T) {
itb := gcf.FromSlice([]int{1, 2, 3})
itb = gcf.Reverse(itb)
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.Reverse(itb)
})
}

func ExampleReverse() {
Expand Down
8 changes: 4 additions & 4 deletions skip.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ type skipIterator[T any] struct {
//
// If count is 0 or negative, returns original Iterable.
func Skip[T any](itb Iterable[T], count int) Iterable[T] {
if itb == nil {
return empty[T]()
if isEmpty(itb) {
return orEmpty(itb)
}
if count < 1 {
return itb
Expand Down Expand Up @@ -67,8 +67,8 @@ type skipWhileIterator[T any] struct {
//
// If whileFunc is nil, returns original Iterable.
func SkipWhile[T any](itb Iterable[T], whileFunc func(v T) bool) Iterable[T] {
if itb == nil {
return empty[T]()
if isEmpty(itb) {
return orEmpty(itb)
}
if whileFunc == nil {
return itb
Expand Down
8 changes: 8 additions & 0 deletions skip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ func TestSkip(t *testing.T) {
itb := gcf.FromSlice([]int{1, 2, 3})
itb = gcf.Skip(itb, 2)
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.Skip(itb, 0)
})
}

func ExampleSkip() {
Expand Down Expand Up @@ -166,6 +170,10 @@ func TestSkipWhile(t *testing.T) {
itb := gcf.FromSlice([]int{1, 2, 3})
itb = gcf.SkipWhile(itb, func(v int) bool { return v < 2 })
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.SkipWhile(itb, func(v int) bool { return true })
})
}

func ExampleSkipWhile() {
Expand Down
8 changes: 8 additions & 0 deletions slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ func TestFromSlice_pointer(t *testing.T) {

itb := gcf.FromSlice([]*int{&i1, &i2, &i3})
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.FromSlice([]int{})
})
}

func TestFromSliceImmutable(t *testing.T) {
Expand Down Expand Up @@ -142,6 +146,10 @@ func TestFromSliceImmutable(t *testing.T) {

itb := gcf.FromSliceImmutable([]int{1, 2, 3})
testBeforeAndAfter(t, itb)

testEmptyChain(t, func(itb gcf.Iterable[int]) gcf.Iterable[int] {
return gcf.FromSliceImmutable([]int{})
})
}

func TestToSlice(t *testing.T) {
Expand Down
12 changes: 6 additions & 6 deletions sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ func (s funcSorter[T]) Sort() { sort.Sort(s) }
// itb := gcf.FromSlice([]int{1, 3, 2})
// itb = gcf.SortAsc(itb)
func SortAsc[T constraints.Ordered](itb Iterable[T]) Iterable[T] {
if itb == nil {
return empty[T]()
if isEmpty(itb) {
return orEmpty(itb)
}
toSorter := func(s []T) sorter[T] { return ascSlice[T](s) }
return &sortIterable[T]{itb, toSorter}
Expand All @@ -72,8 +72,8 @@ func SortAsc[T constraints.Ordered](itb Iterable[T]) Iterable[T] {
// itb := gcf.FromSlice([]int{1, 3, 2})
// itb = gcf.SortDesc(itb)
func SortDesc[T constraints.Ordered](itb Iterable[T]) Iterable[T] {
if itb == nil {
return empty[T]()
if isEmpty(itb) {
return orEmpty(itb)
}
toSorter := func(s []T) sorter[T] { return descSlice[T](s) }
return &sortIterable[T]{itb, toSorter}
Expand All @@ -87,8 +87,8 @@ func SortDesc[T constraints.Ordered](itb Iterable[T]) Iterable[T] {
//
// The less function takes x element and y element, and returns true if x is less than y.
func SortBy[T any](itb Iterable[T], less func(x, y T) bool) Iterable[T] {
if itb == nil {
return empty[T]()
if isEmpty(itb) {
return orEmpty(itb)
}
toSorter := func(s []T) sorter[T] { return funcSorter[T]{s, less} }
return &sortIterable[T]{itb, toSorter}
Expand Down
Loading

0 comments on commit f87c1ae

Please sign in to comment.