From 4bf160919168e8d8e18f0f07508bf9888cb8b0d0 Mon Sep 17 00:00:00 2001 From: David Chase Date: Wed, 20 Dec 2023 16:19:02 -0500 Subject: [PATCH 1/3] optimize Zip to only use one Pull, and test for coverage --- transform.go | 32 ++++++++++++++++++++++++-------- transform_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/transform.go b/transform.go index d6ca43a..dd87ef7 100644 --- a/transform.go +++ b/transform.go @@ -100,20 +100,36 @@ type Zipped[T1, T2 any] struct { // Zip returns a new Seq that yields the values of seq1 and seq2 // simultaneously. func Zip[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { - return func(yield func(Zipped[T1, T2]) bool) { - p1, stop := Pull(seq1) - defer stop() - p2, stop := Pull(seq2) - defer stop() + return func(body func(Zipped[T1, T2]) bool) { + p2, stop2 := Pull(seq2) + defer stop2() + done := false - for { + f := func(v T1) bool { var val Zipped[T1, T2] - val.V1, val.OK1 = p1() + val.V1, val.OK1 = v, true val.V2, val.OK2 = p2() - if (!val.OK1 && !val.OK2) || !yield(val) { + if !body(val) { + done = true + return false + } + return true + } + seq1(f) + if done { + return + } + // seq1 is exhausted + for v2, ok2 := p2(); ok2; v2, ok2 = p2() { + var v1 T1 + var val Zipped[T1, T2] + val.V1, val.OK1 = v1, false + val.V2, val.OK2 = v2, true + if !body(val) { return } } + return } } diff --git a/transform_test.go b/transform_test.go index 71dd676..9b85459 100644 --- a/transform_test.go +++ b/transform_test.go @@ -63,6 +63,35 @@ func TestZip(t *testing.T) { }) } +func TestZipShort1(t *testing.T) { + s1 := OfSlice([]int{1, 2, 3, 4}) + s2 := OfSlice([]int{2, 3, 4, 5, 1}) + seq := Zip(s1, s2) + seq(func(v Zipped[int, int]) bool { + if v.V2-v.V1 != 1 { + t.Fatalf("unexpected values: %+v", v) + } + return true + }) + t.Logf("Greetings from TestZipShort1") +} + +func TestZipShort2(t *testing.T) { + s1 := OfSlice([]int{1, 2, 3, 4, -1, -2}) + s2 := OfSlice([]int{2, 3, 4, 5}) + seq := Zip(s1, s2) + seq(func(v Zipped[int, int]) bool { + if v.V1 == -2 { + return false + } + if v.V2-v.V1 != 1 { + t.Fatalf("unexpected values: %+v", v) + } + return true + }) + t.Logf("Greetings from TestZipShort2") +} + func BenchmarkZip(b *testing.B) { slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{2, 3, 4, 5, 6} From 39ce32ea8db701c32908b6f07d794425d86d70a1 Mon Sep 17 00:00:00 2001 From: David Chase Date: Wed, 20 Dec 2023 21:12:52 -0500 Subject: [PATCH 2/3] Restore old 2pull Zip for comparison and benchmarking --- oldzip_test.go | 1 + transform.go | 20 ++++++++++++++++++++ transform_test.go | 16 ++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/oldzip_test.go b/oldzip_test.go index 3e6f523..96ee504 100644 --- a/oldzip_test.go +++ b/oldzip_test.go @@ -3,6 +3,7 @@ package xiter import "testing" func BenchmarkOldZip(b *testing.B) { + b.ReportAllocs() slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{2, 3, 4, 5, 6} diff --git a/transform.go b/transform.go index dd87ef7..80cac57 100644 --- a/transform.go +++ b/transform.go @@ -97,6 +97,26 @@ type Zipped[T1, T2 any] struct { OK2 bool } +// Zip returns a new Seq that yields the values of seq1 and seq2 +// simultaneously. +func Zip2Pull[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { + return func(yield func(Zipped[T1, T2]) bool) { + p1, stop := Pull(seq1) + defer stop() + p2, stop := Pull(seq2) + defer stop() + + for { + var val Zipped[T1, T2] + val.V1, val.OK1 = p1() + val.V2, val.OK2 = p2() + if (!val.OK1 && !val.OK2) || !yield(val) { + return + } + } + } +} + // Zip returns a new Seq that yields the values of seq1 and seq2 // simultaneously. func Zip[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { diff --git a/transform_test.go b/transform_test.go index 9b85459..fa0ac84 100644 --- a/transform_test.go +++ b/transform_test.go @@ -93,6 +93,7 @@ func TestZipShort2(t *testing.T) { } func BenchmarkZip(b *testing.B) { + b.ReportAllocs() slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{2, 3, 4, 5, 6} @@ -106,6 +107,21 @@ func BenchmarkZip(b *testing.B) { } } +func BenchmarkZip2P(b *testing.B) { + b.ReportAllocs() + slice1 := []int{1, 2, 3, 4, 5} + slice2 := []int{2, 3, 4, 5, 6} + + for i := 0; i < b.N; i++ { + s1 := OfSlice(slice1) + s2 := OfSlice(slice2) + seq := Zip2Pull(s1, s2) + seq(func(v Zipped[int, int]) bool { + return true + }) + } +} + func TestIsSorted(t *testing.T) { if IsSorted(OfSlice([]int{1, 2, 3, 2})) { t.Fatal("is not sorted") From 9ab7394a1e2f8e858c8bbcc483224486a0189b63 Mon Sep 17 00:00:00 2001 From: David Chase Date: Wed, 27 Dec 2023 23:04:44 -0500 Subject: [PATCH 3/3] Incorporate revew feedback; move Zip2Pull into test file. --- oldzip_test.go | 1 - transform.go | 26 +++----------------------- transform_test.go | 16 ---------------- zip2pull_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 40 deletions(-) create mode 100644 zip2pull_test.go diff --git a/oldzip_test.go b/oldzip_test.go index 96ee504..3e6f523 100644 --- a/oldzip_test.go +++ b/oldzip_test.go @@ -3,7 +3,6 @@ package xiter import "testing" func BenchmarkOldZip(b *testing.B) { - b.ReportAllocs() slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{2, 3, 4, 5, 6} diff --git a/transform.go b/transform.go index 80cac57..dcf9976 100644 --- a/transform.go +++ b/transform.go @@ -97,30 +97,10 @@ type Zipped[T1, T2 any] struct { OK2 bool } -// Zip returns a new Seq that yields the values of seq1 and seq2 -// simultaneously. -func Zip2Pull[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { - return func(yield func(Zipped[T1, T2]) bool) { - p1, stop := Pull(seq1) - defer stop() - p2, stop := Pull(seq2) - defer stop() - - for { - var val Zipped[T1, T2] - val.V1, val.OK1 = p1() - val.V2, val.OK2 = p2() - if (!val.OK1 && !val.OK2) || !yield(val) { - return - } - } - } -} - // Zip returns a new Seq that yields the values of seq1 and seq2 // simultaneously. func Zip[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { - return func(body func(Zipped[T1, T2]) bool) { + return func(yield func(Zipped[T1, T2]) bool) { p2, stop2 := Pull(seq2) defer stop2() done := false @@ -129,7 +109,7 @@ func Zip[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { var val Zipped[T1, T2] val.V1, val.OK1 = v, true val.V2, val.OK2 = p2() - if !body(val) { + if !yield(val) { done = true return false } @@ -145,7 +125,7 @@ func Zip[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { var val Zipped[T1, T2] val.V1, val.OK1 = v1, false val.V2, val.OK2 = v2, true - if !body(val) { + if !yield(val) { return } } diff --git a/transform_test.go b/transform_test.go index fa0ac84..9b85459 100644 --- a/transform_test.go +++ b/transform_test.go @@ -93,7 +93,6 @@ func TestZipShort2(t *testing.T) { } func BenchmarkZip(b *testing.B) { - b.ReportAllocs() slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{2, 3, 4, 5, 6} @@ -107,21 +106,6 @@ func BenchmarkZip(b *testing.B) { } } -func BenchmarkZip2P(b *testing.B) { - b.ReportAllocs() - slice1 := []int{1, 2, 3, 4, 5} - slice2 := []int{2, 3, 4, 5, 6} - - for i := 0; i < b.N; i++ { - s1 := OfSlice(slice1) - s2 := OfSlice(slice2) - seq := Zip2Pull(s1, s2) - seq(func(v Zipped[int, int]) bool { - return true - }) - } -} - func TestIsSorted(t *testing.T) { if IsSorted(OfSlice([]int{1, 2, 3, 2})) { t.Fatal("is not sorted") diff --git a/zip2pull_test.go b/zip2pull_test.go new file mode 100644 index 0000000..576baf9 --- /dev/null +++ b/zip2pull_test.go @@ -0,0 +1,38 @@ +package xiter + +import "testing" + +func BenchmarkZip2Pull(b *testing.B) { + b.ReportAllocs() + slice1 := []int{1, 2, 3, 4, 5} + slice2 := []int{2, 3, 4, 5, 6} + + for i := 0; i < b.N; i++ { + s1 := OfSlice(slice1) + s2 := OfSlice(slice2) + seq := zip2Pull(s1, s2) + seq(func(v Zipped[int, int]) bool { + return true + }) + } +} + +// zip2Pull returns a new Seq that yields the values of seq1 and seq2 +// simultaneously. This is the straightforward 2-Pull version. +func zip2Pull[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { + return func(yield func(Zipped[T1, T2]) bool) { + p1, stop := Pull(seq1) + defer stop() + p2, stop := Pull(seq2) + defer stop() + + for { + var val Zipped[T1, T2] + val.V1, val.OK1 = p1() + val.V2, val.OK2 = p2() + if (!val.OK1 && !val.OK2) || !yield(val) { + return + } + } + } +}