Skip to content

Commit ff72035

Browse files
committed
POC: GroupMap fluent api
1 parent 8f9fd9c commit ff72035

File tree

2 files changed

+166
-8
lines changed

2 files changed

+166
-8
lines changed

src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313
package scala.collection
1414
package next
1515

16+
import scala.language.implicitConversions
17+
1618
private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C](
1719
private val col: IterableOnceOps[A, CC, C]
1820
) extends AnyVal {
21+
import NextIterableOnceOpsExtensions.GroupMap
22+
1923
/**
2024
* Partitions this IterableOnce into a map according to a discriminator function `key`. All the values that
2125
* 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](
2832
*
2933
* @note This will force the evaluation of the Iterator.
3034
*/
31-
def groupMapReduce[K, B](key: A => K)(f: A => B)(reduce: (B, B) => B): immutable.Map[K, B] = {
32-
val m = mutable.Map.empty[K, B]
33-
col.foreach { elem =>
34-
m.updateWith(key = key(elem)) {
35-
case Some(b) => Some(reduce(b, f(elem)))
36-
case None => Some(f(elem))
35+
def groupMapReduce[K, B](key: A => K)(f: A => B)(reduce: (B, B) => B): immutable.Map[K, B] =
36+
groupMapTo(key)(f).reduce(reduce)
37+
38+
def groupByTo[K](key: A => K): GroupMap[A, K, A, immutable.Iterable, immutable.Map] =
39+
groupMapTo(key)(identity)
40+
41+
def groupMapTo[K, V](key: A => K)(f: A => V): GroupMap[A, K, V, immutable.Iterable, immutable.Map] =
42+
new GroupMap(col, key, f, immutable.Iterable, immutable.Map)
43+
}
44+
45+
object NextIterableOnceOpsExtensions {
46+
final case class GroupMap[A, K, V, CC[_], MC[_, _]](
47+
col: IterableOnceOps[A, Any, Any],
48+
key: A => K,
49+
f: A => V,
50+
colFactory: Factory[V, CC[V]],
51+
mapFactory: CustomMapFactory[MC, K]
52+
) {
53+
def collectValuesAs[CC1[_]](factory: Factory[V, CC1[V]]): GroupMap[A, K, V, CC1, MC] =
54+
this.copy(colFactory = factory)
55+
56+
def collectResultsAs[MC1[_, _]](factory: CustomMapFactory[MC1, K]): GroupMap[A, K, V, CC, MC1] =
57+
this.copy(mapFactory = factory)
58+
59+
final def result: MC[K, CC[V]] = {
60+
val m = mutable.Map.empty[K, mutable.Builder[V, CC[V]]]
61+
col.foreach { elem =>
62+
val k = key(elem)
63+
val v = f(elem)
64+
m.get(k) match {
65+
case Some(builder) => builder.addOne(v)
66+
case None => m += (k -> colFactory.newBuilder.addOne(v))
67+
}
68+
}
69+
mapFactory.from(m.view.mapValues(_.result()))
70+
}
71+
72+
final def reduce(reduce: (V, V) => V): MC[K, V] = {
73+
val m = mutable.Map.empty[K, V]
74+
col.foreach { elem =>
75+
m.updateWith(key = key(elem)) {
76+
case Some(b) => Some(reduce(b, f(elem)))
77+
case None => Some(f(elem))
78+
}
3779
}
80+
mapFactory.from(m)
3881
}
39-
m.to(immutable.Map)
82+
}
83+
84+
sealed trait CustomMapFactory[MC[_, _], K] {
85+
def from[V](col: IterableOnce[(K, V)]): MC[K, V]
86+
}
87+
88+
object CustomMapFactory {
89+
implicit def fromMapFactory[MC[_, _], K](mf: MapFactory[MC]): CustomMapFactory[MC, K] =
90+
new CustomMapFactory[MC, K] {
91+
override def from[V](col: IterableOnce[(K, V)]): MC[K, V] =
92+
mf.from(col)
93+
}
94+
95+
implicit def fromSortedMapFactory[MC[_, _], K : Ordering](smf: SortedMapFactory[MC]): CustomMapFactory[MC, K] =
96+
new CustomMapFactory[MC, K] {
97+
override def from[V](col: IterableOnce[(K, V)]): MC[K, V] =
98+
smf.from(col)
99+
}
40100
}
41101
}

src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import org.junit.Assert._
1616
import org.junit.Test
1717
import scala.collection.IterableOnceOps
1818
import scala.collection.generic.IsIterableOnce
19+
import scala.collection.immutable.{SortedMap, SortedSet}
1920

2021
final class TestIterableOnceExtensions {
21-
import TestIterableOnceExtensions.LowerCaseString
22+
import TestIterableOnceExtensions._
2223

24+
// groupMapReduce --------------------------------------------
2325
@Test
2426
def iteratorGroupMapReduce(): Unit = {
2527
def occurrences[A](coll: IterableOnce[A]): Map[A, Int] =
@@ -59,6 +61,100 @@ final class TestIterableOnceExtensions {
5961
val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1)
6062
assertEquals(expected, occurrences(xs))
6163
}
64+
// -----------------------------------------------------------
65+
66+
// groupMapTo ------------------------------------------------
67+
@Test
68+
def anyCollectionGroupMapToFull(): Unit = {
69+
def getUniqueUsersByCountrySorted(data: List[Record]): List[(String, List[String])] =
70+
data
71+
.groupMapTo(_.country)(_.user)
72+
.collectValuesAs(SortedSet)
73+
.collectResultsAs(SortedMap)
74+
.result
75+
.view
76+
.mapValues(_.toList)
77+
.toList
78+
79+
val data = List(
80+
Record(user = "Luis", country = "Colombia"),
81+
Record(user = "Seth", country = "USA"),
82+
Record(user = "April", country = "USA"),
83+
Record(user = "Julien", country = "Suisse"),
84+
Record(user = "Rob", country = "USA"),
85+
Record(user = "Seth", country = "USA")
86+
)
87+
88+
val expected = List(
89+
"Colombia" -> List("Luis"),
90+
"Suisse" -> List("Julien"),
91+
"USA" -> List("April", "Rob", "Seth")
92+
)
93+
94+
assertEquals(expected, getUniqueUsersByCountrySorted(data))
95+
}
96+
97+
@Test
98+
def anyCollectionGroupByToFull(): Unit = {
99+
def getUniqueWordsByFirstLetterSorted(data: List[String]): List[(Char, List[String])] =
100+
data
101+
.groupByTo(_.head)
102+
.collectValuesAs(SortedSet)
103+
.collectResultsAs(SortedMap)
104+
.result
105+
.view
106+
.mapValues(_.toList)
107+
.toList
108+
109+
val data = List(
110+
"Autumn",
111+
"Banana",
112+
"April",
113+
"Wilson",
114+
"Apple",
115+
"Apple",
116+
"Winter",
117+
"Banana"
118+
)
119+
120+
val expected = List(
121+
'A' -> List("Apple", "April", "Autumn"),
122+
'B' -> List("Banana"),
123+
'W' -> List("Wilson", "Winter")
124+
)
125+
126+
assertEquals(expected, getUniqueWordsByFirstLetterSorted(data))
127+
}
128+
129+
@Test
130+
def anyCollectionGroupByToReduceFull(): Unit = {
131+
def getAllWordsByFirstLetterSorted(data: List[String]): List[(Char, String)] =
132+
data
133+
.groupByTo(_.head)
134+
.collectResultsAs(SortedMap)
135+
.reduce(_ ++ " " ++ _)
136+
.toList
137+
138+
val data = List(
139+
"Autumn",
140+
"Banana",
141+
"April",
142+
"Wilson",
143+
"Apple",
144+
"Apple",
145+
"Winter",
146+
"Banana"
147+
)
148+
149+
val expected = List(
150+
'A' -> "Autumn April Apple Apple",
151+
'B' -> "Banana Banana",
152+
'W' -> "Wilson Winter"
153+
)
154+
155+
assertEquals(expected, getAllWordsByFirstLetterSorted(data))
156+
}
157+
// -----------------------------------------------------------
62158
}
63159

64160
object TestIterableOnceExtensions {
@@ -81,4 +177,6 @@ object TestIterableOnceExtensions {
81177
override def span(p: Char => Boolean): (String, String) = ???
82178
override def tapEach[U](f: Char => U): String = ???
83179
}
180+
181+
final case class Record(user: String, country: String)
84182
}

0 commit comments

Comments
 (0)