From a0f491d9434ce946f100edbe13fa8e23f6a27907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 7 Dec 2020 17:23:18 -0500 Subject: [PATCH 1/4] POC: GroupMap fluent api --- .../next/NextIterableOnceOpsExtensions.scala | 74 +++++++++++-- .../next/TestIterableOnceExtensions.scala | 100 +++++++++++++++++- 2 files changed, 166 insertions(+), 8 deletions(-) diff --git a/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala index 2bd687d..86f88b8 100644 --- a/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala +++ b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala @@ -13,9 +13,13 @@ package scala.collection package next +import scala.language.implicitConversions + private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C]( private val col: IterableOnceOps[A, CC, C] ) extends AnyVal { + import NextIterableOnceOpsExtensions.GroupMap + /** * Partitions this IterableOnce into a map according to a discriminator function `key`. All the values that * have the same discriminator are then transformed by the `value` function and then reduced into a @@ -28,14 +32,70 @@ private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C]( * * @note This will force the evaluation of the Iterator. */ - def groupMapReduce[K, B](key: A => K)(f: A => B)(reduce: (B, B) => B): immutable.Map[K, B] = { - val m = mutable.Map.empty[K, B] - col.foreach { elem => - m.updateWith(key = key(elem)) { - case Some(b) => Some(reduce(b, f(elem))) - case None => Some(f(elem)) + def groupMapReduce[K, B](key: A => K)(f: A => B)(reduce: (B, B) => B): immutable.Map[K, B] = + groupMapTo(key)(f).reduce(reduce) + + def groupByTo[K](key: A => K): GroupMap[A, K, A, immutable.Iterable, immutable.Map] = + groupMapTo(key)(identity) + + def groupMapTo[K, V](key: A => K)(f: A => V): GroupMap[A, K, V, immutable.Iterable, immutable.Map] = + new GroupMap(col, key, f, immutable.Iterable, immutable.Map) +} + +object NextIterableOnceOpsExtensions { + final case class GroupMap[A, K, V, CC[_], MC[_, _]]( + col: IterableOnceOps[A, Any, Any], + key: A => K, + f: A => V, + colFactory: Factory[V, CC[V]], + mapFactory: CustomMapFactory[MC, K] + ) { + def collectValuesAs[CC1[_]](factory: Factory[V, CC1[V]]): GroupMap[A, K, V, CC1, MC] = + this.copy(colFactory = factory) + + def collectResultsAs[MC1[_, _]](factory: CustomMapFactory[MC1, K]): GroupMap[A, K, V, CC, MC1] = + this.copy(mapFactory = factory) + + final def result: MC[K, CC[V]] = { + val m = mutable.Map.empty[K, mutable.Builder[V, CC[V]]] + col.foreach { elem => + val k = key(elem) + val v = f(elem) + m.get(k) match { + case Some(builder) => builder.addOne(v) + case None => m += (k -> colFactory.newBuilder.addOne(v)) + } + } + mapFactory.from(m.view.mapValues(_.result())) + } + + final def reduce(reduce: (V, V) => V): MC[K, V] = { + val m = mutable.Map.empty[K, V] + col.foreach { elem => + m.updateWith(key = key(elem)) { + case Some(b) => Some(reduce(b, f(elem))) + case None => Some(f(elem)) + } } + mapFactory.from(m) } - m.to(immutable.Map) + } + + sealed trait CustomMapFactory[MC[_, _], K] { + def from[V](col: IterableOnce[(K, V)]): MC[K, V] + } + + object CustomMapFactory { + implicit def fromMapFactory[MC[_, _], K](mf: MapFactory[MC]): CustomMapFactory[MC, K] = + new CustomMapFactory[MC, K] { + override def from[V](col: IterableOnce[(K, V)]): MC[K, V] = + mf.from(col) + } + + implicit def fromSortedMapFactory[MC[_, _], K : Ordering](smf: SortedMapFactory[MC]): CustomMapFactory[MC, K] = + new CustomMapFactory[MC, K] { + override def from[V](col: IterableOnce[(K, V)]): MC[K, V] = + smf.from(col) + } } } diff --git a/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala index 0d8962a..6e40f64 100644 --- a/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala +++ b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala @@ -16,10 +16,12 @@ import org.junit.Assert._ import org.junit.Test import scala.collection.IterableOnceOps import scala.collection.generic.IsIterableOnce +import scala.collection.immutable.{SortedMap, SortedSet} final class TestIterableOnceExtensions { - import TestIterableOnceExtensions.LowerCaseString + import TestIterableOnceExtensions._ + // groupMapReduce -------------------------------------------- @Test def iteratorGroupMapReduce(): Unit = { def occurrences[A](coll: IterableOnce[A]): Map[A, Int] = @@ -59,6 +61,100 @@ final class TestIterableOnceExtensions { val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) assertEquals(expected, occurrences(xs)) } + // ----------------------------------------------------------- + + // groupMapTo ------------------------------------------------ + @Test + def anyCollectionGroupMapToFull(): Unit = { + def getUniqueUsersByCountrySorted(data: List[Record]): List[(String, List[String])] = + data + .groupMapTo(_.country)(_.user) + .collectValuesAs(SortedSet) + .collectResultsAs(SortedMap) + .result + .view + .mapValues(_.toList) + .toList + + val data = List( + Record(user = "Luis", country = "Colombia"), + Record(user = "Seth", country = "USA"), + Record(user = "April", country = "USA"), + Record(user = "Julien", country = "Suisse"), + Record(user = "Rob", country = "USA"), + Record(user = "Seth", country = "USA") + ) + + val expected = List( + "Colombia" -> List("Luis"), + "Suisse" -> List("Julien"), + "USA" -> List("April", "Rob", "Seth") + ) + + assertEquals(expected, getUniqueUsersByCountrySorted(data)) + } + + @Test + def anyCollectionGroupByToFull(): Unit = { + def getUniqueWordsByFirstLetterSorted(data: List[String]): List[(Char, List[String])] = + data + .groupByTo(_.head) + .collectValuesAs(SortedSet) + .collectResultsAs(SortedMap) + .result + .view + .mapValues(_.toList) + .toList + + val data = List( + "Autumn", + "Banana", + "April", + "Wilson", + "Apple", + "Apple", + "Winter", + "Banana" + ) + + val expected = List( + 'A' -> List("Apple", "April", "Autumn"), + 'B' -> List("Banana"), + 'W' -> List("Wilson", "Winter") + ) + + assertEquals(expected, getUniqueWordsByFirstLetterSorted(data)) + } + + @Test + def anyCollectionGroupByToReduceFull(): Unit = { + def getAllWordsByFirstLetterSorted(data: List[String]): List[(Char, String)] = + data + .groupByTo(_.head) + .collectResultsAs(SortedMap) + .reduce(_ ++ " " ++ _) + .toList + + val data = List( + "Autumn", + "Banana", + "April", + "Wilson", + "Apple", + "Apple", + "Winter", + "Banana" + ) + + val expected = List( + 'A' -> "Autumn April Apple Apple", + 'B' -> "Banana Banana", + 'W' -> "Wilson Winter" + ) + + assertEquals(expected, getAllWordsByFirstLetterSorted(data)) + } + // ----------------------------------------------------------- } object TestIterableOnceExtensions { @@ -81,4 +177,6 @@ object TestIterableOnceExtensions { override def span(p: Char => Boolean): (String, String) = ??? override def tapEach[U](f: Char => U): String = ??? } + + final case class Record(user: String, country: String) } From e0356a6790b137b5c23e9afbf4518c072ccbd599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 8 Dec 2020 12:15:55 -0500 Subject: [PATCH 2/4] Update src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala Co-authored-by: Princess | April <7505383+NthPortal@users.noreply.github.com> --- .../scala/collection/next/NextIterableOnceOpsExtensions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala index 86f88b8..0c3501f 100644 --- a/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala +++ b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala @@ -44,7 +44,7 @@ private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C]( object NextIterableOnceOpsExtensions { final case class GroupMap[A, K, V, CC[_], MC[_, _]]( - col: IterableOnceOps[A, Any, Any], + col: IterableOnceOps[A, AnyConstr, _], key: A => K, f: A => V, colFactory: Factory[V, CC[V]], From 301d839d31c0f554a9d70ff5b3ecb4a460f84744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 6 Apr 2021 13:29:23 -0500 Subject: [PATCH 3/4] Splitting the reduce and result operations --- .../next/NextIterableOnceOpsExtensions.scala | 98 +++++++-------- .../next/TestIterableOnceExtensions.scala | 115 +++++++++++------- 2 files changed, 120 insertions(+), 93 deletions(-) diff --git a/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala index 0c3501f..0e3c29f 100644 --- a/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala +++ b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala @@ -13,12 +13,28 @@ package scala.collection package next -import scala.language.implicitConversions - private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C]( private val col: IterableOnceOps[A, CC, C] ) extends AnyVal { - import NextIterableOnceOpsExtensions.GroupMap + import NextIterableOnceOpsExtensions.{GroupMapGen, GroupMapGenGen} + + def groupBy[K](key: A => K)(implicit valuesFactory: Factory[A, C]): immutable.Map[K, C] = + groupByGen(key).result + + def groupByGen[K](key: A => K)(implicit valuesFactory: Factory[A, C]): GroupMapGen[A, K, A, C] = + groupByGenGen(key).collectValuesAs(valuesFactory) + + def groupByGenGen[K](key: A => K): GroupMapGenGen[A, K, A] = + groupMapGenGen(key)(identity) + + def groupMap[K, V](key: A => K)(f: A => V)(implicit valuesFactory: Factory[V, CC[V]]): immutable.Map[K, CC[V]] = + groupMapGen(key)(f).result + + def groupMapGen[K, V](key: A => K)(f: A => V)(implicit valuesFactory: Factory[V, CC[V]]): GroupMapGen[A, K, V, CC[V]] = + groupMapGenGen(key)(f).collectValuesAs(valuesFactory) + + def groupMapGenGen[K, V](key: A => K)(f: A => V): GroupMapGenGen[A, K, V] = + new GroupMapGenGen(col, key, f) /** * Partitions this IterableOnce into a map according to a discriminator function `key`. All the values that @@ -32,44 +48,20 @@ private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C]( * * @note This will force the evaluation of the Iterator. */ - def groupMapReduce[K, B](key: A => K)(f: A => B)(reduce: (B, B) => B): immutable.Map[K, B] = - groupMapTo(key)(f).reduce(reduce) - - def groupByTo[K](key: A => K): GroupMap[A, K, A, immutable.Iterable, immutable.Map] = - groupMapTo(key)(identity) - - def groupMapTo[K, V](key: A => K)(f: A => V): GroupMap[A, K, V, immutable.Iterable, immutable.Map] = - new GroupMap(col, key, f, immutable.Iterable, immutable.Map) + def groupMapReduce[K, V](key: A => K)(f: A => V)(reduce: (V, V) => V): immutable.Map[K, V] = + groupMapGenGen(key)(f).reduceValues(reduce) } -object NextIterableOnceOpsExtensions { - final case class GroupMap[A, K, V, CC[_], MC[_, _]]( +private[next] object NextIterableOnceOpsExtensions { + final class GroupMapGenGen[A, K, V] private[NextIterableOnceOpsExtensions]( col: IterableOnceOps[A, AnyConstr, _], key: A => K, - f: A => V, - colFactory: Factory[V, CC[V]], - mapFactory: CustomMapFactory[MC, K] + f: A => V ) { - def collectValuesAs[CC1[_]](factory: Factory[V, CC1[V]]): GroupMap[A, K, V, CC1, MC] = - this.copy(colFactory = factory) - - def collectResultsAs[MC1[_, _]](factory: CustomMapFactory[MC1, K]): GroupMap[A, K, V, CC, MC1] = - this.copy(mapFactory = factory) + def reduceValues(reduce: (V, V) => V): immutable.Map[K, V] = + reduceValuesAs(immutable.Map)(reduce) - final def result: MC[K, CC[V]] = { - val m = mutable.Map.empty[K, mutable.Builder[V, CC[V]]] - col.foreach { elem => - val k = key(elem) - val v = f(elem) - m.get(k) match { - case Some(builder) => builder.addOne(v) - case None => m += (k -> colFactory.newBuilder.addOne(v)) - } - } - mapFactory.from(m.view.mapValues(_.result())) - } - - final def reduce(reduce: (V, V) => V): MC[K, V] = { + def reduceValuesAs[MC](resultFactory: Factory[(K, V), MC])(reduce: (V, V) => V): MC = { val m = mutable.Map.empty[K, V] col.foreach { elem => m.updateWith(key = key(elem)) { @@ -77,25 +69,33 @@ object NextIterableOnceOpsExtensions { case None => Some(f(elem)) } } - mapFactory.from(m) + resultFactory.fromSpecific(m) } - } - sealed trait CustomMapFactory[MC[_, _], K] { - def from[V](col: IterableOnce[(K, V)]): MC[K, V] + def collectValuesAs[C](valuesFactory: Factory[V, C]): GroupMapGen[A, K, V, C] = + new GroupMapGen(col, key, f, valuesFactory) } - object CustomMapFactory { - implicit def fromMapFactory[MC[_, _], K](mf: MapFactory[MC]): CustomMapFactory[MC, K] = - new CustomMapFactory[MC, K] { - override def from[V](col: IterableOnce[(K, V)]): MC[K, V] = - mf.from(col) - } + final class GroupMapGen[A, K, V, C] private[NextIterableOnceOpsExtensions]( + col: IterableOnceOps[A, AnyConstr, _], + key: A => K, + f: A => V, + valuesFactory: Factory[V, C] + ) { + def result: immutable.Map[K, C] = + resultAs(immutable.Map) - implicit def fromSortedMapFactory[MC[_, _], K : Ordering](smf: SortedMapFactory[MC]): CustomMapFactory[MC, K] = - new CustomMapFactory[MC, K] { - override def from[V](col: IterableOnce[(K, V)]): MC[K, V] = - smf.from(col) + def resultAs[MC](resultFactory: Factory[(K, C), MC]): MC = { + val m = mutable.Map.empty[K, mutable.Builder[V, C]] + col.foreach { elem => + val k = key(elem) + val v = f(elem) + m.get(k) match { + case Some(builder) => builder.addOne(v) + case None => m.update(key = k, value = valuesFactory.newBuilder.addOne(v)) + } } + resultFactory.fromSpecific(m.view.mapValues(_.result())) + } } } diff --git a/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala index 6e40f64..0478f8e 100644 --- a/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala +++ b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala @@ -16,7 +16,7 @@ import org.junit.Assert._ import org.junit.Test import scala.collection.IterableOnceOps import scala.collection.generic.IsIterableOnce -import scala.collection.immutable.{SortedMap, SortedSet} +import scala.collection.immutable.{ArraySeq, BitSet, SortedMap, SortedSet} final class TestIterableOnceExtensions { import TestIterableOnceExtensions._ @@ -24,54 +24,57 @@ final class TestIterableOnceExtensions { // groupMapReduce -------------------------------------------- @Test def iteratorGroupMapReduce(): Unit = { - def occurrences[A](coll: IterableOnce[A]): Map[A, Int] = - coll.iterator.groupMapReduce(identity)(_ => 1)(_ + _) + def occurrences[A](data: IterableOnce[A]): Map[A, Int] = + data.iterator.groupMapReduce(identity)(_ => 1)(_ + _) - val xs = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b') + val data = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b') val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) - assertEquals(expected, occurrences(xs)) + + assertEquals(expected, occurrences(data)) } @Test def iterableOnceOpsGroupMapReduce(): Unit = { - def occurrences[A, CC[_], C](coll: IterableOnceOps[A, CC, C]): Map[A, Int] = - coll.groupMapReduce(identity)(_ => 1)(_ + _) + def occurrences[A, CC[_], C](data: IterableOnceOps[A, CC, C]): Map[A, Int] = + data.groupMapReduce(identity)(_ => 1)(_ + _) - val xs = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b') + val data = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b') val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) - assertEquals(expected, occurrences(xs)) + + assertEquals(expected, occurrences(data)) } @Test def anyLikeIterableOnceGroupMapReduce(): Unit = { - def occurrences[Repr](coll: Repr)(implicit it: IsIterableOnce[Repr]): Map[it.A, Int] = - it(coll).iterator.groupMapReduce(identity)(_ => 1)(_ + _) + def occurrences[Repr](data: Repr)(implicit it: IsIterableOnce[Repr]): Map[it.A, Int] = + it(data).iterator.groupMapReduce(identity)(_ => 1)(_ + _) - val xs = "abbcaaab" + val data = "abbcaaab" val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) - assertEquals(expected, occurrences(xs)) + + assertEquals(expected, occurrences(data)) } @Test def customIterableOnceOpsGroupMapReduce(): Unit = { - def occurrences(coll: LowerCaseString): Map[Char, Int] = - coll.groupMapReduce(identity)(_ => 1)(_ + _) + def occurrences(data: LowerCaseString): Map[Char, Int] = + data.groupMapReduce(identity)(_ => 1)(_ + _) - val xs = LowerCaseString("abBcAaAb") + val data = LowerCaseString("abBcAaAb") val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) - assertEquals(expected, occurrences(xs)) + + assertEquals(expected, occurrences(data)) } // ----------------------------------------------------------- - // groupMapTo ------------------------------------------------ + // GroupMapGenGen -------------------------------------------- @Test - def anyCollectionGroupMapToFull(): Unit = { + def anyCollectionGroupMapGenResultAs(): Unit = { def getUniqueUsersByCountrySorted(data: List[Record]): List[(String, List[String])] = data - .groupMapTo(_.country)(_.user) + .groupMapGenGen(_.country)(_.user) .collectValuesAs(SortedSet) - .collectResultsAs(SortedMap) - .result + .resultAs(SortedMap) .view .mapValues(_.toList) .toList @@ -95,15 +98,11 @@ final class TestIterableOnceExtensions { } @Test - def anyCollectionGroupByToFull(): Unit = { - def getUniqueWordsByFirstLetterSorted(data: List[String]): List[(Char, List[String])] = + def anyCollectionGroupMapGenGenReduce(): Unit = { + def getAllWordsByFirstLetterSorted(data: List[String]): List[(Char, String)] = data - .groupByTo(_.head) - .collectValuesAs(SortedSet) - .collectResultsAs(SortedMap) - .result - .view - .mapValues(_.toList) + .groupByGenGen(_.head) + .reduceValuesAs(SortedMap)(_ ++ " " ++ _) .toList val data = List( @@ -116,23 +115,51 @@ final class TestIterableOnceExtensions { "Winter", "Banana" ) - val expected = List( - 'A' -> List("Apple", "April", "Autumn"), - 'B' -> List("Banana"), - 'W' -> List("Wilson", "Winter") + 'A' -> "Autumn April Apple Apple", + 'B' -> "Banana Banana", + 'W' -> "Wilson Winter" ) - assertEquals(expected, getUniqueWordsByFirstLetterSorted(data)) + assertEquals(expected, getAllWordsByFirstLetterSorted(data)) } @Test - def anyCollectionGroupByToReduceFull(): Unit = { - def getAllWordsByFirstLetterSorted(data: List[String]): List[(Char, String)] = + def iterableOnceOpsGroupByGenSpecificFactory(): Unit = { + def bitsByEven(data: BitSet): Map[Boolean, BitSet] = + data.groupByGen(x => (x % 2) == 0).result + + val data = BitSet(1, 2, 3, 4, 5) + val expected = Map( + true -> BitSet(2, 4), + false -> BitSet(1, 3, 5) + ) + + assertEquals(expected, bitsByEven(data)) + } + + @Test + def iterableOnceOpsGroupMapGenIterableFactory(): Unit = { + def bitsByEvenAsChars(data: BitSet): Map[Boolean, Set[Char]] = + data.groupMapGen(x => (x % 2) == 0)(_.toChar).result + + val data = BitSet(100, 101, 102, 103, 104, 105) + val expected = Map( + true -> Set('d', 'f', 'h'), + false -> Set('e', 'g', 'i') + ) + + assertEquals(expected, bitsByEvenAsChars(data)) + } + + @Test + def iteratorGroupBy(): Unit = { + def getUniqueWordsByFirstLetter(data: IterableOnce[String]): List[(Char, Set[String])] = data - .groupByTo(_.head) - .collectResultsAs(SortedMap) - .reduce(_ ++ " " ++ _) + .iterator + .groupBy(_.head) + .view + .mapValues(_.toSet) .toList val data = List( @@ -147,12 +174,12 @@ final class TestIterableOnceExtensions { ) val expected = List( - 'A' -> "Autumn April Apple Apple", - 'B' -> "Banana Banana", - 'W' -> "Wilson Winter" + 'A' -> Set("Apple", "April", "Autumn"), + 'B' -> Set("Banana"), + 'W' -> Set("Wilson", "Winter") ) - assertEquals(expected, getAllWordsByFirstLetterSorted(data)) + assertEquals(expected, getUniqueWordsByFirstLetter(data)) } // ----------------------------------------------------------- } From 05184053c557ea5ac62732b0e4cf306f16076543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Wed, 28 Apr 2021 11:08:49 -0500 Subject: [PATCH 4/4] Applying naming suggestions --- .../next/NextIterableOnceOpsExtensions.scala | 51 +++++++++---------- .../next/TestIterableOnceExtensions.scala | 22 ++++---- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala index 0e3c29f..c675f5c 100644 --- a/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala +++ b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala @@ -16,25 +16,25 @@ package next private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C]( private val col: IterableOnceOps[A, CC, C] ) extends AnyVal { - import NextIterableOnceOpsExtensions.{GroupMapGen, GroupMapGenGen} + import NextIterableOnceOpsExtensions.{GroupMapToView, GroupMapView} - def groupBy[K](key: A => K)(implicit valuesFactory: Factory[A, C]): immutable.Map[K, C] = - groupByGen(key).result + def groupBy[K](key: A => K)(implicit groupsFactory: Factory[A, C]): immutable.Map[K, C] = + viewGroupByTo(key).toMap - def groupByGen[K](key: A => K)(implicit valuesFactory: Factory[A, C]): GroupMapGen[A, K, A, C] = - groupByGenGen(key).collectValuesAs(valuesFactory) + def viewGroupByTo[K](key: A => K)(implicit groupsFactory: Factory[A, C]): GroupMapToView[A, K, A, C] = + viewGroupBy(key).collectGroupsTo(groupsFactory) - def groupByGenGen[K](key: A => K): GroupMapGenGen[A, K, A] = - groupMapGenGen(key)(identity) + def viewGroupBy[K](key: A => K): GroupMapView[A, K, A] = + viewGroupMap(key)(identity) - def groupMap[K, V](key: A => K)(f: A => V)(implicit valuesFactory: Factory[V, CC[V]]): immutable.Map[K, CC[V]] = - groupMapGen(key)(f).result + def groupMap[K, V](key: A => K)(f: A => V)(implicit groupsFactory: Factory[V, CC[V]]): immutable.Map[K, CC[V]] = + viewGroupMapTo(key)(f).toMap - def groupMapGen[K, V](key: A => K)(f: A => V)(implicit valuesFactory: Factory[V, CC[V]]): GroupMapGen[A, K, V, CC[V]] = - groupMapGenGen(key)(f).collectValuesAs(valuesFactory) + def viewGroupMapTo[K, V](key: A => K)(f: A => V)(implicit groupsFactory: Factory[V, CC[V]]): GroupMapToView[A, K, V, CC[V]] = + viewGroupMap(key)(f).collectGroupsTo(groupsFactory) - def groupMapGenGen[K, V](key: A => K)(f: A => V): GroupMapGenGen[A, K, V] = - new GroupMapGenGen(col, key, f) + def viewGroupMap[K, V](key: A => K)(f: A => V): GroupMapView[A, K, V] = + new GroupMapView(col, key, f) /** * Partitions this IterableOnce into a map according to a discriminator function `key`. All the values that @@ -49,19 +49,16 @@ private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C]( * @note This will force the evaluation of the Iterator. */ def groupMapReduce[K, V](key: A => K)(f: A => V)(reduce: (V, V) => V): immutable.Map[K, V] = - groupMapGenGen(key)(f).reduceValues(reduce) + viewGroupMap(key)(f).reduceValuesTo(immutable.Map)(reduce) } private[next] object NextIterableOnceOpsExtensions { - final class GroupMapGenGen[A, K, V] private[NextIterableOnceOpsExtensions]( + final class GroupMapView[A, K, V] private[NextIterableOnceOpsExtensions]( col: IterableOnceOps[A, AnyConstr, _], key: A => K, f: A => V ) { - def reduceValues(reduce: (V, V) => V): immutable.Map[K, V] = - reduceValuesAs(immutable.Map)(reduce) - - def reduceValuesAs[MC](resultFactory: Factory[(K, V), MC])(reduce: (V, V) => V): MC = { + def reduceValuesTo[MC](resultFactory: Factory[(K, V), MC])(reduce: (V, V) => V): MC = { val m = mutable.Map.empty[K, V] col.foreach { elem => m.updateWith(key = key(elem)) { @@ -72,27 +69,27 @@ private[next] object NextIterableOnceOpsExtensions { resultFactory.fromSpecific(m) } - def collectValuesAs[C](valuesFactory: Factory[V, C]): GroupMapGen[A, K, V, C] = - new GroupMapGen(col, key, f, valuesFactory) + def collectGroupsTo[C](groupsFactory: Factory[V, C]): GroupMapToView[A, K, V, C] = + new GroupMapToView(col, key, f, groupsFactory) } - final class GroupMapGen[A, K, V, C] private[NextIterableOnceOpsExtensions]( + final class GroupMapToView[A, K, V, C] private[NextIterableOnceOpsExtensions]( col: IterableOnceOps[A, AnyConstr, _], key: A => K, f: A => V, - valuesFactory: Factory[V, C] + groupsFactory: Factory[V, C] ) { - def result: immutable.Map[K, C] = - resultAs(immutable.Map) + def toMap: immutable.Map[K, C] = + to(immutable.Map) - def resultAs[MC](resultFactory: Factory[(K, C), MC]): MC = { + def to[MC](resultFactory: Factory[(K, C), MC]): MC = { val m = mutable.Map.empty[K, mutable.Builder[V, C]] col.foreach { elem => val k = key(elem) val v = f(elem) m.get(k) match { case Some(builder) => builder.addOne(v) - case None => m.update(key = k, value = valuesFactory.newBuilder.addOne(v)) + case None => m.update(key = k, value = groupsFactory.newBuilder.addOne(v)) } } resultFactory.fromSpecific(m.view.mapValues(_.result())) diff --git a/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala index 0478f8e..d626ec5 100644 --- a/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala +++ b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala @@ -69,12 +69,12 @@ final class TestIterableOnceExtensions { // GroupMapGenGen -------------------------------------------- @Test - def anyCollectionGroupMapGenResultAs(): Unit = { + def anyCollectionGroupMapToViewTo(): Unit = { def getUniqueUsersByCountrySorted(data: List[Record]): List[(String, List[String])] = data - .groupMapGenGen(_.country)(_.user) - .collectValuesAs(SortedSet) - .resultAs(SortedMap) + .viewGroupMap(_.country)(_.user) + .collectGroupsTo(SortedSet) + .to(SortedMap) .view .mapValues(_.toList) .toList @@ -98,11 +98,11 @@ final class TestIterableOnceExtensions { } @Test - def anyCollectionGroupMapGenGenReduce(): Unit = { + def anyCollectionGroupMapViewReduceValuesTo(): Unit = { def getAllWordsByFirstLetterSorted(data: List[String]): List[(Char, String)] = data - .groupByGenGen(_.head) - .reduceValuesAs(SortedMap)(_ ++ " " ++ _) + .viewGroupBy(_.head) + .reduceValuesTo(SortedMap)(_ ++ " " ++ _) .toList val data = List( @@ -125,9 +125,9 @@ final class TestIterableOnceExtensions { } @Test - def iterableOnceOpsGroupByGenSpecificFactory(): Unit = { + def iterableOnceOpsViewGroupByToSpecificFactoryToMap(): Unit = { def bitsByEven(data: BitSet): Map[Boolean, BitSet] = - data.groupByGen(x => (x % 2) == 0).result + data.viewGroupByTo(x => (x % 2) == 0).toMap val data = BitSet(1, 2, 3, 4, 5) val expected = Map( @@ -139,9 +139,9 @@ final class TestIterableOnceExtensions { } @Test - def iterableOnceOpsGroupMapGenIterableFactory(): Unit = { + def iterableOnceOpsViewGroupMapToIterableFactoryToMap(): Unit = { def bitsByEvenAsChars(data: BitSet): Map[Boolean, Set[Char]] = - data.groupMapGen(x => (x % 2) == 0)(_.toChar).result + data.viewGroupMapTo(x => (x % 2) == 0)(_.toChar).toMap val data = BitSet(100, 101, 102, 103, 104, 105) val expected = Map(