From 7d1f06d03b5f9d3a611049926ffe11004cbb73c1 Mon Sep 17 00:00:00 2001 From: Marcelo Cenerino Date: Mon, 30 Oct 2017 15:03:51 +0000 Subject: [PATCH] Add lazyZip operation --- README.md | 2 +- .../immutable/HashSetBenchmark.scala | 15 + .../immutable/ImmutableArrayBenchmark.scala | 15 + .../immutable/LazyListBenchmark.scala | 15 + .../collection/immutable/ListBenchmark.scala | 15 + .../immutable/NumericRangeBenchmark.scala | 23 ++ .../collection/immutable/RangeBenchmark.scala | 23 ++ .../immutable/ScalaHashSetBenchmark.scala | 19 +- .../immutable/ScalaListBenchmark.scala | 19 +- .../immutable/ScalaTreeSetBenchmark.scala | 19 +- .../immutable/ScalaVectorBenchmark.scala | 19 +- .../immutable/TreeSetBenchmark.scala | 15 + .../immutable/VectorBenchmark.scala | 15 + .../mutable/ArrayBufferBenchmark.scala | 15 + .../mutable/ListBufferBenchmark.scala | 15 + .../mutable/ScalaArrayBenchmark.scala | 19 +- .../scala/strawman/collection/ArrayOps.scala | 2 +- .../scala/strawman/collection/Iterable.scala | 21 +- .../strawman/collection/LazyZipOps.scala | 363 ++++++++++++++++++ .../main/scala/strawman/collection/Map.scala | 5 +- .../scala/strawman/collection/SortedSet.scala | 4 +- .../collection/immutable/ImmutableArray.scala | 6 +- .../collection/immutable/LazyList.scala | 6 +- .../strawman/collection/LazyZipOpsTest.scala | 260 +++++++++++++ 24 files changed, 899 insertions(+), 31 deletions(-) create mode 100644 collections/src/main/scala/strawman/collection/LazyZipOps.scala create mode 100644 test/junit/src/test/scala/strawman/collection/LazyZipOpsTest.scala diff --git a/README.md b/README.md index 9c39df8340..8d3251fc96 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ For more information, see the [CONTRIBUTING](CONTRIBUTING.md) file. - [x] `sliding` - [x] `unzip` - [x] `values` / `valuesIterator` -- [x] `zip` / `zipWithIndex` +- [x] `zip` / `zipWithIndex` / `lazyZip` ### In-place mutating operations diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/HashSetBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/HashSetBenchmark.scala index c5d2cfb795..0c02eab96e 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/HashSetBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/HashSetBenchmark.scala @@ -135,6 +135,21 @@ class HashSetBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume(xs.lazyZip(xs).map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/ImmutableArrayBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/ImmutableArrayBenchmark.scala index f01577c227..01ecdee72b 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/ImmutableArrayBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/ImmutableArrayBenchmark.scala @@ -273,6 +273,21 @@ class ImmutableArrayBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume(xs.lazyZip(xs).map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/LazyListBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/LazyListBenchmark.scala index 77a5d81341..f453b9c40a 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/LazyListBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/LazyListBenchmark.scala @@ -273,6 +273,21 @@ class LazyListBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume(xs.lazyZip(xs).map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/ListBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/ListBenchmark.scala index df14ce5daa..61565440fe 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/ListBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/ListBenchmark.scala @@ -273,6 +273,21 @@ class ListBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume(xs.lazyZip(xs).map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/NumericRangeBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/NumericRangeBenchmark.scala index 5241aa9559..28020432d9 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/NumericRangeBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/NumericRangeBenchmark.scala @@ -21,12 +21,14 @@ class NumericRangeBenchmark { var xs: NumericRange[Int] = _ var zs: NumericRange[Int] = _ var randomIndices: scala.Array[Int] = _ + var zipped: IndexedSeq[(Long, Long)] = _ def fresh(n: Int) = NumericRange.inclusive(1, n, 1) @Setup(Level.Trial) def initTrial(): Unit = { xs = fresh(size) zs = NumericRange.inclusive(-1, (-size / 1000) min -2, -1) + zipped = xs.map(x => (x, x)) if (size > 0) { randomIndices = scala.Array.fill(1000)(scala.util.Random.nextInt(size)) } @@ -176,4 +178,25 @@ class NumericRangeBenchmark { val result = xs.groupBy(_ % 5) bh.consume(result) } + + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Int, b: Int) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = { + val xs1: IndexedSeq[Int] = xs + bh.consume(xs1.lazyZip(xs1).map((_, _))) + } + + @Benchmark + def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) } \ No newline at end of file diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/RangeBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/RangeBenchmark.scala index 33a24d0989..d9ed0fbc91 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/RangeBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/RangeBenchmark.scala @@ -21,12 +21,14 @@ class RangeBenchmark { var xs: Range = _ var zs: Range = _ var randomIndices: scala.Array[Int] = _ + var zipped: IndexedSeq[(Long, Long)] = _ def fresh(n: Int) = Range.inclusive(1, n, 1) @Setup(Level.Trial) def initTrial(): Unit = { xs = fresh(size) zs = Range.inclusive(-1, (-size / 1000) min -2, -1) + zipped = xs.map(x => (x, x)) if (size > 0) { randomIndices = scala.Array.fill(1000)(scala.util.Random.nextInt(size)) } @@ -176,4 +178,25 @@ class RangeBenchmark { val result = xs.groupBy(_ % 5) bh.consume(result) } + + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Int, b: Int) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = { + val xs1: IndexedSeq[Int] = xs + bh.consume(xs1.lazyZip(xs1).map((_, _))) + } + + @Benchmark + def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) } \ No newline at end of file diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaHashSetBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaHashSetBenchmark.scala index a4c5b4e8d5..0a193f7f46 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaHashSetBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaHashSetBenchmark.scala @@ -5,8 +5,8 @@ import java.util.concurrent.TimeUnit import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole -import scala.{Any, AnyRef, Int, Long, Unit, math} -import scala.Predef.intWrapper +import scala.{Any, AnyRef, Int, Long, Tuple2, Unit, math} +import scala.Predef.{intWrapper, $conforms, tuple2ToZippedOps} @BenchmarkMode(scala.Array(Mode.AverageTime)) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -135,6 +135,21 @@ class ScalaHashSetBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume((xs, xs).zipped.map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip(t => (t._1, t._2))) diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaListBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaListBenchmark.scala index b50dd92ac5..1c44f850f0 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaListBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaListBenchmark.scala @@ -5,8 +5,8 @@ import java.util.concurrent.TimeUnit import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole -import scala.{Any, AnyRef, Int, Long, Unit, math} -import scala.Predef.{intWrapper, $conforms} +import scala.{Any, AnyRef, Int, Long, Tuple2, Unit, math} +import scala.Predef.{intWrapper, $conforms, tuple2ToZippedOps} @BenchmarkMode(scala.Array(Mode.AverageTime)) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -270,6 +270,21 @@ class ScalaListBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume((xs, xs).zipped.map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaTreeSetBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaTreeSetBenchmark.scala index e0c02cb229..061d6550c8 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaTreeSetBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaTreeSetBenchmark.scala @@ -5,8 +5,8 @@ import java.util.concurrent.TimeUnit import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole -import scala.{Any, AnyRef, Int, Long, Unit, math} -import scala.Predef.intWrapper +import scala.{Any, AnyRef, Int, Long, Tuple2, Unit, math} +import scala.Predef.{intWrapper, tuple2ToZippedOps, $conforms} @BenchmarkMode(scala.Array(Mode.AverageTime)) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -135,6 +135,21 @@ class ScalaTreeSetBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume((xs, xs).zipped.map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip(t => (t._1, t._2))) diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaVectorBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaVectorBenchmark.scala index 664d449db2..9afe931450 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaVectorBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/ScalaVectorBenchmark.scala @@ -5,8 +5,8 @@ import java.util.concurrent.TimeUnit import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole -import scala.{Any, AnyRef, Int, Long, Unit, math} -import scala.Predef.{intWrapper, $conforms} +import scala.{Any, AnyRef, Int, Long, Tuple2, Unit, math} +import scala.Predef.{intWrapper, $conforms, tuple2ToZippedOps} @BenchmarkMode(scala.Array(Mode.AverageTime)) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -270,6 +270,21 @@ class ScalaVectorBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume((xs, xs).zipped.map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/TreeSetBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/TreeSetBenchmark.scala index 859031b391..2dd4dd4dec 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/TreeSetBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/TreeSetBenchmark.scala @@ -135,6 +135,21 @@ class TreeSetBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume(xs.lazyZip(xs).map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/VectorBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/VectorBenchmark.scala index e494d591a6..ff8fb68b3c 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/VectorBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/VectorBenchmark.scala @@ -273,6 +273,21 @@ class VectorBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume(xs.lazyZip(xs).map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/mutable/ArrayBufferBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/mutable/ArrayBufferBenchmark.scala index 5a61422529..f8a3531f31 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/mutable/ArrayBufferBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/mutable/ArrayBufferBenchmark.scala @@ -273,6 +273,21 @@ class ArrayBufferBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume(xs.lazyZip(xs).map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/mutable/ListBufferBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/mutable/ListBufferBenchmark.scala index 6111986c19..225286ff8a 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/mutable/ListBufferBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/mutable/ListBufferBenchmark.scala @@ -273,6 +273,21 @@ class ListBufferBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume(xs.lazyZip(xs).map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip) diff --git a/benchmarks/time/src/main/scala/strawman/collection/mutable/ScalaArrayBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/mutable/ScalaArrayBenchmark.scala index 8477429c93..11c4bfd44e 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/mutable/ScalaArrayBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/mutable/ScalaArrayBenchmark.scala @@ -5,8 +5,8 @@ import java.util.concurrent.TimeUnit import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole -import scala.{Any, AnyRef, Int, Long, Unit, math} -import scala.Predef.{intWrapper, longArrayOps, wrapLongArray, wrapRefArray} +import scala.{Any, AnyRef, Int, Long, Unit, math, Tuple2} +import scala.Predef.{intWrapper, longArrayOps, tuple2ToZippedOps, wrapLongArray, wrapRefArray} import scala.Predef.{intWrapper, longArrayOps} @@ -272,6 +272,21 @@ class ScalaArrayBenchmark { } } + @Benchmark + def transform_zip(bh: Blackhole): Unit = bh.consume(xs.zip(xs)) + + @Benchmark + def transform_zipMapTupled(bh: Blackhole): Unit = { + val f = (a: Long, b: Long) => (a, b) + bh.consume(xs.zip(xs).map(f.tupled)) + } + + @Benchmark + def transform_zipWithIndex(bh: Blackhole): Unit = bh.consume(xs.zipWithIndex) + + @Benchmark + def transform_lazyZip(bh: Blackhole): Unit = bh.consume((xs, xs).zipped.map((_, _))) + @Benchmark def transform_unzip(bh: Blackhole): Unit = bh.consume(zipped.unzip(t => (t._1, t._2))) diff --git a/collections/src/main/scala/strawman/collection/ArrayOps.scala b/collections/src/main/scala/strawman/collection/ArrayOps.scala index 61ffb65128..88914f61f8 100644 --- a/collections/src/main/scala/strawman/collection/ArrayOps.scala +++ b/collections/src/main/scala/strawman/collection/ArrayOps.scala @@ -52,7 +52,7 @@ class ArrayOps[A](val xs: Array[A]) def ++[B >: A : ClassTag](xs: Iterable[B]): Array[B] = fromTaggedIterable(View.Concat(toIterable, xs)) - def zip[B: ClassTag](xs: Iterable[B]): Array[(A, B)] = fromTaggedIterable(View.Zip(toIterable, xs)) + def zip[B: ClassTag](that: Iterable[B]): Array[(A, B)] = fromTaggedIterable(View.Zip(toIterable, that)) } diff --git a/collections/src/main/scala/strawman/collection/Iterable.scala b/collections/src/main/scala/strawman/collection/Iterable.scala index e3a08033f6..a854fe1ade 100644 --- a/collections/src/main/scala/strawman/collection/Iterable.scala +++ b/collections/src/main/scala/strawman/collection/Iterable.scala @@ -2,6 +2,7 @@ package strawman package collection import scala.annotation.unchecked.uncheckedVariance +import scala.language.implicitConversions import scala.reflect.ClassTag import scala.{Any, Array, Boolean, `inline`, Int, None, Numeric, Option, Ordering, PartialFunction, StringContext, Some, Unit, deprecated, IllegalArgumentException, Function1, AnyRef} import java.lang.{String, UnsupportedOperationException} @@ -961,19 +962,18 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] { * by combining corresponding elements in pairs. * If one of the two collections is longer than the other, its remaining elements are ignored. * - * @param xs The iterable providing the second half of each result pair + * @param that The iterable providing the second half of each result pair * @tparam B the type of the second half of the returned pairs - * @return a new collection of type `That` containing pairs consisting of - * corresponding elements of this $coll and `that`. The length - * of the returned collection is the minimum of the lengths of this $coll and `that`. + * @return a new $coll containing pairs consisting of corresponding elements of this $coll and `that`. + * The length of the returned collection is the minimum of the lengths of this $coll and `that`. */ - def zip[B](xs: Iterable[B]): CC[(A @uncheckedVariance, B)] = fromIterable(View.Zip(toIterable, xs)) + def zip[B](that: Iterable[B]): CC[(A @uncheckedVariance, B)] = fromIterable(View.Zip(toIterable, that)) // sound bcs of VarianceNote /** Zips this $coll with its indices. * - * @return A new collection of type `That` containing pairs consisting of all elements of this - * $coll paired with their index. Indices start at `0`. + * @return A new $coll containing pairs consisting of all elements of this $coll paired with their index. + * Indices start at `0`. * @example * `List("a", "b", "c").zipWithIndex == List(("a", 0), ("b", 1), ("c", 2))` */ @@ -1002,9 +1002,10 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] { val unzipped = View.Unzip(toIterable) (fromIterable(unzipped.first), fromIterable(unzipped.second)) } - } -object Iterable extends IterableFactory.Delegate[Iterable](immutable.Iterable) +object Iterable extends IterableFactory.Delegate[Iterable](immutable.Iterable) { + implicit def toLazyZipOps[A, CC[X] <: Iterable[X]](that: CC[A]): LazyZipOps[A, CC[A]] = new LazyZipOps(that) +} -abstract class AbstractIterable[+A] extends Iterable[A] +abstract class AbstractIterable[+A] extends Iterable[A] \ No newline at end of file diff --git a/collections/src/main/scala/strawman/collection/LazyZipOps.scala b/collections/src/main/scala/strawman/collection/LazyZipOps.scala new file mode 100644 index 0000000000..7a5f314d8b --- /dev/null +++ b/collections/src/main/scala/strawman/collection/LazyZipOps.scala @@ -0,0 +1,363 @@ +package strawman.collection + +import scala.{AnyVal, Boolean, Int, StringContext, Unit} +import scala.language.implicitConversions +import scala.Predef.intWrapper + +final class LazyZipOps[A, C1 <: Iterable[A]] private[collection](val `this`: C1) extends AnyVal { + + /** Analogous to `zip` except that the elements in each collection are not consumed until a strict operation is + * invoked on the returned `LazyZip2` decorator. + * + * Calls to `lazyZip` can be chained to support higher arities (up to 4) without incurring the expense of + * constructing and deconstructing intermediary tuples. + * + * {{{ + * val xs = List(1, 2, 3) + * val res = (xs lazyZip xs lazyZip xs lazyZip xs).map((a, b, c, d) => a + b + c + d) + * // res == List(4, 8, 12) + * }}} + * + * @param that the iterable providing the second element of each eventual pair + * @tparam B the type of the second element in each eventual pair + * @return a decorator `LazyZip2` that allows strict operations to be performed on the lazily evaluated pairs + * or chained calls to `lazyZip`. Implicit conversion to `Iterable[(A, B)]` is also supported. + */ + def lazyZip[B](that: Iterable[B]): LazyZip2[A, B, C1] = new LazyZip2(`this`, that) +} + +/** Decorator representing lazily zipped pairs. */ +final class LazyZip2[El1, El2, C1 <: Iterable[El1]] private[collection](coll1: C1, coll2: Iterable[El2]) { + + /** Zips `that` iterable collection with an existing `LazyZip2`. The elements in each collection are + * not consumed until a strict operation is invoked on the returned `LazyZip3` decorator. + * + * @param that the iterable providing the third element of each eventual triple + * @tparam B the type of the third element in each eventual triple + * @return a decorator `LazyZip3` that allows strict operations to be performed on the lazily evaluated tuples or + * chained calls to `lazyZip`. Implicit conversion to `Iterable[(El1, El2, B)]` is also supported. + */ + def lazyZip[B](that: Iterable[B]): LazyZip3[El1, El2, B, C1] = new LazyZip3(coll1, coll2, that) + + def map[B, C](f: (El1, El2) => B)(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecificIterable(coll1)(new View[B] { + def iterator() = new Iterator[B] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + def hasNext = elems1.hasNext && elems2.hasNext + def next() = f(elems1.next(), elems2.next()) + } + override def knownSize: Int = coll1.knownSize min coll2.knownSize + }) + } + + def flatMap[B, C](f: (El1, El2) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecificIterable(coll1)(new View[B] { + def iterator() = new Iterator[B] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private var _current: Iterator[B] = Iterator.empty + private def current = { + while (!_current.hasNext && elems1.hasNext && elems2.hasNext) + _current = f(elems1.next(), elems2.next()).iterator() + _current + } + def hasNext = current.hasNext + def next() = current.next() + } + }) + } + + def filter[C](p: (El1, El2) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2), C]): C = { + bf.fromSpecificIterable(coll1)(new View[(El1, El2)] { + def iterator() = new Iterator[(El1, El2)] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private var _current: (El1, El2) = _ + private def current = { + while ((_current eq null) && elems1.hasNext && elems2.hasNext) { + val e1 = elems1.next() + val e2 = elems2.next() + if (p(e1, e2)) _current = (e1, e2) + } + _current + } + def hasNext = current ne null + def next() = { + val c = current + if (c ne null) { + _current = null + c + } else Iterator.empty.next() + } + } + }) + } + + def exists(p: (El1, El2) => Boolean): Boolean = { + val elems1 = coll1.iterator() + val elems2 = coll2.iterator() + var res = false + + while (!res && elems1.hasNext && elems2.hasNext) res = p(elems1.next(), elems2.next()) + + res + } + + def forall(p: (El1, El2) => Boolean): Boolean = !exists((el1, el2) => !p(el1, el2)) + + def foreach[U](f: (El1, El2) => U): Unit = { + val elems1 = coll1.iterator() + val elems2 = coll2.iterator() + + while (elems1.hasNext && elems2.hasNext) f(elems1.next(), elems2.next()) + } + + private def toIterable = new View[(El1, El2)] { + def iterator() = new Iterator[(El1, El2)] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + def hasNext = elems1.hasNext && elems2.hasNext + def next() = (elems1.next(), elems2.next()) + } + override def knownSize: Int = coll1.knownSize min coll2.knownSize + } + + override def toString = s"$coll1.lazyZip($coll2)" +} + +object LazyZip2 { + implicit def lazyZip2ToIterable[El1, El2](zipped2: LazyZip2[El1, El2, _]): View[(El1, El2)] = zipped2.toIterable +} + + +/** Decorator representing lazily zipped triples. */ +final class LazyZip3[El1, El2, El3, C1 <: Iterable[El1]] private[collection](coll1: C1, + coll2: Iterable[El2], + coll3: Iterable[El3]) { + + /** Zips `that` iterable collection with an existing `LazyZip3`. The elements in each collection are + * not consumed until a strict operation is invoked on the returned `LazyZip4` decorator. + * + * @param that the iterable providing the fourth element of each eventual 4-tuple + * @tparam B the type of the fourth element in each eventual 4-tuple + * @return a decorator `LazyZip4` that allows strict operations to be performed on the lazily evaluated tuples. + * Implicit conversion to `Iterable[(El1, El2, El3, B)]` is also supported. + */ + def lazyZip[B](that: Iterable[B]): LazyZip4[El1, El2, El3, B, C1] = new LazyZip4(coll1, coll2, coll3, that) + + def map[B, C](f: (El1, El2, El3) => B)(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecificIterable(coll1)(new View[B] { + def iterator() = new Iterator[B] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private val elems3 = coll3.iterator() + def hasNext = elems1.hasNext && elems2.hasNext && elems3.hasNext + def next() = f(elems1.next(), elems2.next(), elems3.next()) + } + override def knownSize: Int = coll1.knownSize min coll2.knownSize min coll3.knownSize + }) + } + + def flatMap[B, C](f: (El1, El2, El3) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecificIterable(coll1)(new View[B] { + def iterator() = new Iterator[B] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private val elems3 = coll3.iterator() + private var _current: Iterator[B] = Iterator.empty + private def current = { + while (!_current.hasNext && elems1.hasNext && elems2.hasNext && elems3.hasNext) + _current = f(elems1.next(), elems2.next(), elems3.next()).iterator() + _current + } + def hasNext = current.hasNext + def next() = current.next() + } + }) + } + + def filter[C](p: (El1, El2, El3) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2, El3), C]): C = { + bf.fromSpecificIterable(coll1)(new View[(El1, El2, El3)] { + def iterator() = new Iterator[(El1, El2, El3)] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private val elems3 = coll3.iterator() + private var _current: (El1, El2, El3) = _ + private def current = { + while ((_current eq null) && elems1.hasNext && elems2.hasNext && elems3.hasNext) { + val e1 = elems1.next() + val e2 = elems2.next() + val e3 = elems3.next() + if (p(e1, e2, e3)) _current = (e1, e2, e3) + } + _current + } + def hasNext = current ne null + def next() = { + val c = current + if (c ne null) { + _current = null + c + } else Iterator.empty.next() + } + } + }) + } + + def exists(p: (El1, El2, El3) => Boolean): Boolean = { + val elems1 = coll1.iterator() + val elems2 = coll2.iterator() + val elems3 = coll3.iterator() + var res = false + + while (!res && elems1.hasNext && elems2.hasNext && elems3.hasNext) + res = p(elems1.next(), elems2.next(), elems3.next()) + + res + } + + def forall(p: (El1, El2, El3) => Boolean): Boolean = !exists((el1, el2, el3) => !p(el1, el2, el3)) + + def foreach[U](f: (El1, El2, El3) => U): Unit = { + val elems1 = coll1.iterator() + val elems2 = coll2.iterator() + val elems3 = coll3.iterator() + + while (elems1.hasNext && elems2.hasNext && elems3.hasNext) + f(elems1.next(), elems2.next(), elems3.next()) + } + + private def toIterable = new View[(El1, El2, El3)] { + def iterator() = new Iterator[(El1, El2, El3)] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private val elems3 = coll3.iterator() + def hasNext = elems1.hasNext && elems2.hasNext && elems3.hasNext + def next() = (elems1.next(), elems2.next(), elems3.next()) + } + override def knownSize: Int = coll1.knownSize min coll2.knownSize min coll3.knownSize + } + + override def toString = s"$coll1.lazyZip($coll2).lazyZip($coll3)" +} + +object LazyZip3 { + implicit def lazyZip3ToIterable[El1, El2, El3](zipped3: LazyZip3[El1, El2, El3, _]): View[(El1, El2, El3)] = zipped3.toIterable +} + + + +/** Decorator representing lazily zipped 4-tuples. */ +final class LazyZip4[El1, El2, El3, El4, C1 <: Iterable[El1]] private[collection](coll1: C1, + coll2: Iterable[El2], + coll3: Iterable[El3], + coll4: Iterable[El4]) { + + def map[B, C](f: (El1, El2, El3, El4) => B)(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecificIterable(coll1)(new View[B] { + def iterator() = new Iterator[B] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private val elems3 = coll3.iterator() + private val elems4 = coll4.iterator() + def hasNext = elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext + def next() = f(elems1.next(), elems2.next(), elems3.next(), elems4.next()) + } + override def knownSize: Int = coll1.knownSize min coll2.knownSize min coll3.knownSize min coll4.knownSize + }) + } + + def flatMap[B, C](f: (El1, El2, El3, El4) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecificIterable(coll1)(new View[B] { + def iterator() = new Iterator[B] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private val elems3 = coll3.iterator() + private val elems4 = coll4.iterator() + private var _current: Iterator[B] = Iterator.empty + private def current = { + while (!_current.hasNext && elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext) + _current = f(elems1.next(), elems2.next(), elems3.next(), elems4.next()).iterator() + _current + } + def hasNext = current.hasNext + def next() = current.next() + } + }) + } + + def filter[C](p: (El1, El2, El3, El4) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2, El3, El4), C]): C = { + bf.fromSpecificIterable(coll1)(new View[(El1, El2, El3, El4)] { + def iterator() = new Iterator[(El1, El2, El3, El4)] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private val elems3 = coll3.iterator() + private val elems4 = coll4.iterator() + private var _current: (El1, El2, El3, El4) = _ + private def current = { + while ((_current eq null) && elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext) { + val e1 = elems1.next() + val e2 = elems2.next() + val e3 = elems3.next() + val e4 = elems4.next() + if (p(e1, e2, e3, e4)) _current = (e1, e2, e3, e4) + } + _current + } + def hasNext = current ne null + def next() = { + val c = current + if (c ne null) { + _current = null + c + } else Iterator.empty.next() + } + } + }) + } + + def exists(p: (El1, El2, El3, El4) => Boolean): Boolean = { + val elems1 = coll1.iterator() + val elems2 = coll2.iterator() + val elems3 = coll3.iterator() + val elems4 = coll4.iterator() + var res = false + + while (!res && elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext) + res = p(elems1.next(), elems2.next(), elems3.next(), elems4.next()) + + res + } + + def forall(p: (El1, El2, El3, El4) => Boolean): Boolean = !exists((el1, el2, el3, el4) => !p(el1, el2, el3, el4)) + + def foreach[U](f: (El1, El2, El3, El4) => U): Unit = { + val elems1 = coll1.iterator() + val elems2 = coll2.iterator() + val elems3 = coll3.iterator() + val elems4 = coll4.iterator() + + while (elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext) + f(elems1.next(), elems2.next(), elems3.next(), elems4.next()) + } + + private def toIterable = new View[(El1, El2, El3, El4)] { + def iterator() = new Iterator[(El1, El2, El3, El4)] { + private val elems1 = coll1.iterator() + private val elems2 = coll2.iterator() + private val elems3 = coll3.iterator() + private val elems4 = coll4.iterator() + def hasNext = elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext + def next() = (elems1.next(), elems2.next(), elems3.next(), elems4.next()) + } + override def knownSize: Int = coll1.knownSize min coll2.knownSize min coll3.knownSize min coll4.knownSize + } + + override def toString = s"$coll1.lazyZip($coll2).lazyZip($coll3).lazyZip($coll4)" +} + +object LazyZip4 { + implicit def lazyZip4ToIterable[El1, El2, El3, El4](zipped4: LazyZip4[El1, El2, El3, El4, _]): View[(El1, El2, El3, El4)] = + zipped4.toIterable +} \ No newline at end of file diff --git a/collections/src/main/scala/strawman/collection/Map.scala b/collections/src/main/scala/strawman/collection/Map.scala index 3dd1eb2318..0d69fde7fb 100644 --- a/collections/src/main/scala/strawman/collection/Map.scala +++ b/collections/src/main/scala/strawman/collection/Map.scala @@ -6,6 +6,7 @@ import collection.mutable.Builder import scala.{Any, Boolean, ClassCastException, Equals, Int, NoSuchElementException, None, Nothing, Option, Ordering, PartialFunction, Serializable, Some, StringContext, `inline`, throws} import scala.Predef.String import scala.annotation.unchecked.uncheckedVariance +import scala.language.implicitConversions import scala.util.hashing.MurmurHash3 /** Base Map type */ @@ -242,7 +243,9 @@ trait MapOps[K, +V, +CC[X, Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]] * @define coll map * @define Coll `Map` */ -object Map extends MapFactory.Delegate[Map](immutable.Map) +object Map extends MapFactory.Delegate[Map](immutable.Map) { + implicit def toLazyZipOps[K, V, CC[X, Y] <: Iterable[(X, Y)]](that: CC[K, V]): LazyZipOps[(K, V), CC[K, V]] = new LazyZipOps(that) +} /** Explicit instantiation of the `Map` trait to reduce class file size in subclasses. */ abstract class AbstractMap[A, +B] extends AbstractIterable[(A, B)] with Map[A, B] diff --git a/collections/src/main/scala/strawman/collection/SortedSet.scala b/collections/src/main/scala/strawman/collection/SortedSet.scala index 0223d2e1ac..7387072c20 100644 --- a/collections/src/main/scala/strawman/collection/SortedSet.scala +++ b/collections/src/main/scala/strawman/collection/SortedSet.scala @@ -40,8 +40,8 @@ trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, CC, C]] def flatMap[B : Ordering](f: A => IterableOnce[B]): CC[B] = sortedFromIterable(View.FlatMap(toIterable, f)) /** Zip. Interesting because it requires to align to source collections. */ - def zip[B](xs: Iterable[B])(implicit ev: Ordering[(A @uncheckedVariance, B)]): CC[(A @uncheckedVariance, B)] = // sound bcs of VarianceNote - sortedFromIterable(View.Zip(toIterable, xs)) + def zip[B](that: Iterable[B])(implicit ev: Ordering[(A @uncheckedVariance, B)]): CC[(A @uncheckedVariance, B)] = // sound bcs of VarianceNote + sortedFromIterable(View.Zip(toIterable, that)) def collect[B: Ordering](pf: scala.PartialFunction[A, B]): CC[B] = flatMap(a => if (pf.isDefinedAt(a)) View.Single(pf(a)) diff --git a/collections/src/main/scala/strawman/collection/immutable/ImmutableArray.scala b/collections/src/main/scala/strawman/collection/immutable/ImmutableArray.scala index eb532f1225..d6673e9e5c 100644 --- a/collections/src/main/scala/strawman/collection/immutable/ImmutableArray.scala +++ b/collections/src/main/scala/strawman/collection/immutable/ImmutableArray.scala @@ -79,14 +79,14 @@ class ImmutableArray[+A] private[collection] (private val elements: scala.Array[ fromIterable(View.Concat(xs, toIterable)) } - override def zip[B](xs: collection.Iterable[B]): ImmutableArray[(A, B)] = - xs match { + override def zip[B](that: collection.Iterable[B]): ImmutableArray[(A, B)] = + that match { case bs: ImmutableArray[B] => ImmutableArray.tabulate(length min bs.length) { i => (apply(i), bs(i)) } case _ => - fromIterable(View.Zip(toIterable, xs)) + fromIterable(View.Zip(toIterable, that)) } override def partition(p: A => Boolean): (ImmutableArray[A], ImmutableArray[A]) = { diff --git a/collections/src/main/scala/strawman/collection/immutable/LazyList.scala b/collections/src/main/scala/strawman/collection/immutable/LazyList.scala index d6b8fdb157..7ffc3c6fa3 100644 --- a/collections/src/main/scala/strawman/collection/immutable/LazyList.scala +++ b/collections/src/main/scala/strawman/collection/immutable/LazyList.scala @@ -346,9 +346,9 @@ sealed abstract class LazyList[+A] else prefix.lazyAppendAll(nonEmptyPrefix.tail.flatMap(f)) } - override final def zip[B](xs: collection.Iterable[B]): LazyList[(A, B)] = - if (this.isEmpty || xs.isEmpty) LazyList.empty - else LazyList.cons((this.head, xs.head), this.tail.zip(xs.tail)) + override final def zip[B](that: collection.Iterable[B]): LazyList[(A, B)] = + if (this.isEmpty || that.isEmpty) LazyList.empty + else LazyList.cons((this.head, that.head), this.tail.zip(that.tail)) override final def zipWithIndex: LazyList[(A, Int)] = this.zip(LazyList.from(0)) diff --git a/test/junit/src/test/scala/strawman/collection/LazyZipOpsTest.scala b/test/junit/src/test/scala/strawman/collection/LazyZipOpsTest.scala new file mode 100644 index 0000000000..f0d9dd5987 --- /dev/null +++ b/test/junit/src/test/scala/strawman/collection/LazyZipOpsTest.scala @@ -0,0 +1,260 @@ +package strawman.collection + +import org.hamcrest.CoreMatchers._ +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import strawman.collection.immutable._ + +@RunWith(classOf[JUnit4]) +class LazyZipOpsTest { + + private val ws = List(1, 2, 3) + private val xs = List(1, 2, 3, 4, 5, 6) + private val ys = List("a", "b", "c", "d", "e", "f") + private val zs = List(true, false, true, false, true, false) + private val zipped2 = ws lazyZip xs + private val zipped3 = ws lazyZip xs lazyZip ys + private val zipped4 = ws lazyZip xs lazyZip ys lazyZip zs + private val map = Map(1 -> "foo" , 2 -> "bar") + private val sortedMap = TreeMap(1 -> "foo" , 2 -> "bar") + private val sortedSet = TreeSet(1, 2, 3) + + @Test + def lazyZip2_map(): Unit = { + val res: List[(Int, Int)] = zipped2.map((a, b) => (a, b)) + + assertEquals(List((1, 1), (2, 2), (3, 3)), res) + } + + @Test + def lazyZip2_flatMap(): Unit = { + val res: List[(Int, Int)] = zipped2.flatMap((a, b) => List((a, b))) + + assertEquals(List((1, 1), (2, 2), (3, 3)), res) + } + + @Test + def lazyZip2_filter(): Unit = { + val res: List[(Int, Int)] = zipped2.filter((a, _) => a % 2 == 0) + + assertEquals(List((2, 2)), res) + } + + @Test + def lazyZip2_exists(): Unit = { + assertTrue(zipped2.exists((a, b) => a + b > 5)) + assertFalse(zipped2.exists((a, b) => a + b < 0)) + } + + @Test + def lazyZip2_forall(): Unit = { + assertTrue(zipped2.forall((a, b) => a + b > 0)) + assertFalse(zipped2.forall((a, b) => a + b > 2)) + } + + @Test + def lazyZip2_foreach(): Unit = { + var res = "" + zipped2.foreach((a, b) => res += s"[$a,$b]") + + assertEquals("[1,1][2,2][3,3]", res) + } + + @Test + def lazyZip2_toIterable(): Unit = { + val iter: Iterable[(Int, Int)] = zipped2 + + assertEquals(List((1, 1), (2, 2), (3, 3)), iter.to(List)) + } + + @Test + def lazyZip2_empty(): Unit = { + assertTrue(Nil.lazyZip(xs).isEmpty) + assertTrue(xs.lazyZip(Nil).isEmpty) + } + + @Test + def lazyZip2_withOrdering(): Unit = { + val res: TreeSet[Int] = sortedSet.lazyZip(ws).map(_ + _) + + assertEquals(TreeSet(2, 4, 6), res) + } + + @Test + def lazyZip2_withMap(): Unit = { + val res: Map[Int, (String, String)] = map.lazyZip(ys).map { case ((k, v), s) => k -> (s, v) } + + assertThat(res, either( + is(Map(1 -> ("a", "foo"), 2 -> ("b", "bar")))) + .or(is(Map(1 -> ("b", "foo"), 2 -> ("a", "bar")))) + ) + } + + @Test + def lazyZip2_withSortedMap(): Unit = { + val res: TreeMap[Int, (String, String)] = sortedMap.lazyZip(ys).map { case ((k, v), s) => k -> (s, v) } + + assertEquals(Map(1 -> ("a", "foo"), 2 -> ("b", "bar")), res) + } + + @Test + def lazyZip3_map(): Unit = { + val res: List[(Int, Int, String)] = zipped3.map((a, b, c) => (a, b, c)) + + assertEquals(List((1, 1, "a"), (2, 2, "b"), (3, 3, "c")), res) + } + + @Test + def lazyZip3_flatMap(): Unit = { + val res: List[(Int, Int, String)] = zipped3.flatMap((a, b, c) => List((a, b, c))) + + assertEquals(List((1, 1, "a"), (2, 2, "b"), (3, 3, "c")), res) + } + + @Test + def lazyZip3_filter(): Unit = { + val res: List[(Int, Int, String)] = zipped3.filter((a, _, _) => a % 2 != 0) + + assertEquals(List((1, 1, "a"), (3, 3, "c")), res) + } + + @Test + def lazyZip3_exists(): Unit = { + assertTrue(zipped3.exists((a, b, _) => a + b > 5)) + assertFalse(zipped3.exists((a, b, _) => a + b < 0)) + } + + @Test + def lazyZip3_forall(): Unit = { + assertTrue(zipped3.forall((a, b, _) => (a + b) % 2 == 0)) + assertFalse(zipped3.forall((a, b, _) => a + b > 5)) + } + + @Test + def lazyZip3_foreach(): Unit = { + var res = "" + zipped3.foreach((a, b, c) => res += s"[$a,$b,$c]") + + assertEquals("[1,1,a][2,2,b][3,3,c]", res) + } + + @Test + def lazyZip3_toIterable(): Unit = { + val iter: Iterable[(Int, Int, String)] = zipped3 + + assertEquals(List((1, 1, "a"), (2, 2, "b"), (3, 3, "c")), iter.to(List)) + } + + @Test + def lazyZip3_empty(): Unit = { + assertTrue(zipped2.lazyZip(Nil).isEmpty) + assertTrue(Nil.lazyZip(Nil).lazyZip(xs).isEmpty) + } + + @Test + def lazyZip3_withOrdering(): Unit = { + val res: TreeSet[Int] = sortedSet.lazyZip(xs).lazyZip(ws).map(_ + _ + _) + + assertEquals(TreeSet(3, 6, 9), res) + } + + @Test + def lazyZip3_withMap(): Unit = { + val res: Map[Int, (Int, String, String)] = map.lazyZip(ws).lazyZip(ys).map { case ((k, v), w, y) => k -> (w, y, v) } + + assertThat(res, either( + is(Map(1 -> (1, "a", "foo"), 2 -> (2, "b", "bar")))) + .or(is(Map(1 -> (2, "b", "foo"), 2 -> (1, "a", "bar")))) + ) + } + + @Test + def lazyZip3_withSortedMap(): Unit = { + val res: TreeMap[Int, (Int, String, String)] = sortedMap.lazyZip(ws).lazyZip(ys) + .map { case ((k, v), w, y) => k -> (w, y, v) } + + assertEquals(Map(1 -> (1, "a", "foo"), 2 -> (2, "b", "bar")), res) + } + + @Test + def lazyZip4_map(): Unit = { + val res: List[(Int, Int, String, Boolean)] = zipped4.map((a, b, c, d) => (a, b, c, d)) + + assertEquals(List((1, 1, "a", true), (2, 2, "b", false), (3, 3, "c", true)), res) + } + + @Test + def lazyZip4_flatMap(): Unit = { + val res: List[(Int, Int, String, Boolean)] = zipped4.flatMap((a, b, c, d) => List((a, b, c, d))) + + assertEquals(List((1, 1, "a", true), (2, 2, "b", false), (3, 3, "c", true)), res) + } + + @Test + def lazyZip4_filter(): Unit = { + val res: List[(Int, Int, String, Boolean)] = zipped4.filter((_, _, _, d) => d) + + assertEquals(List((1, 1, "a", true), (3, 3, "c", true)), res) + } + + @Test + def lazyZip4_exists(): Unit = { + assertTrue(zipped4.exists((a, b, c, d) => a + b > 5 && !c.isEmpty && d)) + assertFalse(zipped4.exists((a, b, c, d) => a + b > 5 && !c.isEmpty && !d)) + } + + @Test + def lazyZip4_forall(): Unit = { + assertTrue(zipped4.forall((a, b, _, _) => (a + b) % 2 == 0)) + assertFalse(zipped4.forall((a, b, _, d) => a + b > 0 && d)) + } + + @Test + def lazyZip4_foreach(): Unit = { + var res = "" + zipped4.foreach((a, b, c, d) => res += s"[$a,$b,$c,$d]") + + assertEquals("[1,1,a,true][2,2,b,false][3,3,c,true]", res) + } + + @Test + def lazyZip4_toIterable(): Unit = { + val iter: Iterable[(Int, Int, String, Boolean)] = zipped4 + + assertEquals(List((1, 1, "a", true), (2, 2, "b", false), (3, 3, "c", true)), iter.to(List)) + } + + @Test + def lazyZip4_empty(): Unit = { + assertTrue(zipped3.lazyZip(Nil).isEmpty) + assertTrue(Nil.lazyZip(Nil).lazyZip(Nil).lazyZip(xs).isEmpty) + } + + @Test + def lazyZip4_withOrdering(): Unit = { + val res: TreeSet[Int] = sortedSet.lazyZip(xs).lazyZip(ws).lazyZip(ws).map(_ + _ + _ + _) + + assertEquals(TreeSet(4, 8, 12), res) + } + + @Test + def lazyZip4_withMap(): Unit = { + val res: Map[Int, (Int, Int, String, String)] = map.lazyZip(ws).lazyZip(xs).lazyZip(ys) + .map { case ((k, v), w, x, y) => k -> (w, x, y, v) } + + assertThat(res, either( + is(Map(1 -> (1, 1, "a", "foo"), 2 -> (2, 2, "b", "bar")))) + .or(is(Map(1 -> (2, 2, "b", "foo"), 2 -> (1, 1, "a", "bar")))) + ) + } + + @Test + def lazyZip4_withSortedMap(): Unit = { + val res: TreeMap[Int, (Int, Int, String, String)] = sortedMap.lazyZip(ws).lazyZip(xs).lazyZip(ys) + .map { case ((k, v), w, x, y) => k -> (w, x, y, v) } + + assertEquals(Map(1 -> (1, 1, "a", "foo"), 2 -> (2, 2, "b", "bar")), res) + } +} \ No newline at end of file