Skip to content

Commit 301d839

Browse files
committed
Splitting the reduce and result operations
1 parent e0356a6 commit 301d839

File tree

2 files changed

+120
-93
lines changed

2 files changed

+120
-93
lines changed

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

+49-49
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,28 @@
1313
package scala.collection
1414
package next
1515

16-
import scala.language.implicitConversions
17-
1816
private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C](
1917
private val col: IterableOnceOps[A, CC, C]
2018
) extends AnyVal {
21-
import NextIterableOnceOpsExtensions.GroupMap
19+
import NextIterableOnceOpsExtensions.{GroupMapGen, GroupMapGenGen}
20+
21+
def groupBy[K](key: A => K)(implicit valuesFactory: Factory[A, C]): immutable.Map[K, C] =
22+
groupByGen(key).result
23+
24+
def groupByGen[K](key: A => K)(implicit valuesFactory: Factory[A, C]): GroupMapGen[A, K, A, C] =
25+
groupByGenGen(key).collectValuesAs(valuesFactory)
26+
27+
def groupByGenGen[K](key: A => K): GroupMapGenGen[A, K, A] =
28+
groupMapGenGen(key)(identity)
29+
30+
def groupMap[K, V](key: A => K)(f: A => V)(implicit valuesFactory: Factory[V, CC[V]]): immutable.Map[K, CC[V]] =
31+
groupMapGen(key)(f).result
32+
33+
def groupMapGen[K, V](key: A => K)(f: A => V)(implicit valuesFactory: Factory[V, CC[V]]): GroupMapGen[A, K, V, CC[V]] =
34+
groupMapGenGen(key)(f).collectValuesAs(valuesFactory)
35+
36+
def groupMapGenGen[K, V](key: A => K)(f: A => V): GroupMapGenGen[A, K, V] =
37+
new GroupMapGenGen(col, key, f)
2238

2339
/**
2440
* Partitions this IterableOnce into a map according to a discriminator function `key`. All the values that
@@ -32,70 +48,54 @@ private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C](
3248
*
3349
* @note This will force the evaluation of the Iterator.
3450
*/
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)
51+
def groupMapReduce[K, V](key: A => K)(f: A => V)(reduce: (V, V) => V): immutable.Map[K, V] =
52+
groupMapGenGen(key)(f).reduceValues(reduce)
4353
}
4454

45-
object NextIterableOnceOpsExtensions {
46-
final case class GroupMap[A, K, V, CC[_], MC[_, _]](
55+
private[next] object NextIterableOnceOpsExtensions {
56+
final class GroupMapGenGen[A, K, V] private[NextIterableOnceOpsExtensions](
4757
col: IterableOnceOps[A, AnyConstr, _],
4858
key: A => K,
49-
f: A => V,
50-
colFactory: Factory[V, CC[V]],
51-
mapFactory: CustomMapFactory[MC, K]
59+
f: A => V
5260
) {
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)
61+
def reduceValues(reduce: (V, V) => V): immutable.Map[K, V] =
62+
reduceValuesAs(immutable.Map)(reduce)
5863

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] = {
64+
def reduceValuesAs[MC](resultFactory: Factory[(K, V), MC])(reduce: (V, V) => V): MC = {
7365
val m = mutable.Map.empty[K, V]
7466
col.foreach { elem =>
7567
m.updateWith(key = key(elem)) {
7668
case Some(b) => Some(reduce(b, f(elem)))
7769
case None => Some(f(elem))
7870
}
7971
}
80-
mapFactory.from(m)
72+
resultFactory.fromSpecific(m)
8173
}
82-
}
8374

84-
sealed trait CustomMapFactory[MC[_, _], K] {
85-
def from[V](col: IterableOnce[(K, V)]): MC[K, V]
75+
def collectValuesAs[C](valuesFactory: Factory[V, C]): GroupMapGen[A, K, V, C] =
76+
new GroupMapGen(col, key, f, valuesFactory)
8677
}
8778

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-
}
79+
final class GroupMapGen[A, K, V, C] private[NextIterableOnceOpsExtensions](
80+
col: IterableOnceOps[A, AnyConstr, _],
81+
key: A => K,
82+
f: A => V,
83+
valuesFactory: Factory[V, C]
84+
) {
85+
def result: immutable.Map[K, C] =
86+
resultAs(immutable.Map)
9487

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)
88+
def resultAs[MC](resultFactory: Factory[(K, C), MC]): MC = {
89+
val m = mutable.Map.empty[K, mutable.Builder[V, C]]
90+
col.foreach { elem =>
91+
val k = key(elem)
92+
val v = f(elem)
93+
m.get(k) match {
94+
case Some(builder) => builder.addOne(v)
95+
case None => m.update(key = k, value = valuesFactory.newBuilder.addOne(v))
96+
}
9997
}
98+
resultFactory.fromSpecific(m.view.mapValues(_.result()))
99+
}
100100
}
101101
}

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

+71-44
Original file line numberDiff line numberDiff line change
@@ -16,62 +16,65 @@ 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}
19+
import scala.collection.immutable.{ArraySeq, BitSet, SortedMap, SortedSet}
2020

2121
final class TestIterableOnceExtensions {
2222
import TestIterableOnceExtensions._
2323

2424
// groupMapReduce --------------------------------------------
2525
@Test
2626
def iteratorGroupMapReduce(): Unit = {
27-
def occurrences[A](coll: IterableOnce[A]): Map[A, Int] =
28-
coll.iterator.groupMapReduce(identity)(_ => 1)(_ + _)
27+
def occurrences[A](data: IterableOnce[A]): Map[A, Int] =
28+
data.iterator.groupMapReduce(identity)(_ => 1)(_ + _)
2929

30-
val xs = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b')
30+
val data = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b')
3131
val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1)
32-
assertEquals(expected, occurrences(xs))
32+
33+
assertEquals(expected, occurrences(data))
3334
}
3435

3536
@Test
3637
def iterableOnceOpsGroupMapReduce(): Unit = {
37-
def occurrences[A, CC[_], C](coll: IterableOnceOps[A, CC, C]): Map[A, Int] =
38-
coll.groupMapReduce(identity)(_ => 1)(_ + _)
38+
def occurrences[A, CC[_], C](data: IterableOnceOps[A, CC, C]): Map[A, Int] =
39+
data.groupMapReduce(identity)(_ => 1)(_ + _)
3940

40-
val xs = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b')
41+
val data = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b')
4142
val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1)
42-
assertEquals(expected, occurrences(xs))
43+
44+
assertEquals(expected, occurrences(data))
4345
}
4446

4547
@Test
4648
def anyLikeIterableOnceGroupMapReduce(): Unit = {
47-
def occurrences[Repr](coll: Repr)(implicit it: IsIterableOnce[Repr]): Map[it.A, Int] =
48-
it(coll).iterator.groupMapReduce(identity)(_ => 1)(_ + _)
49+
def occurrences[Repr](data: Repr)(implicit it: IsIterableOnce[Repr]): Map[it.A, Int] =
50+
it(data).iterator.groupMapReduce(identity)(_ => 1)(_ + _)
4951

50-
val xs = "abbcaaab"
52+
val data = "abbcaaab"
5153
val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1)
52-
assertEquals(expected, occurrences(xs))
54+
55+
assertEquals(expected, occurrences(data))
5356
}
5457

5558
@Test
5659
def customIterableOnceOpsGroupMapReduce(): Unit = {
57-
def occurrences(coll: LowerCaseString): Map[Char, Int] =
58-
coll.groupMapReduce(identity)(_ => 1)(_ + _)
60+
def occurrences(data: LowerCaseString): Map[Char, Int] =
61+
data.groupMapReduce(identity)(_ => 1)(_ + _)
5962

60-
val xs = LowerCaseString("abBcAaAb")
63+
val data = LowerCaseString("abBcAaAb")
6164
val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1)
62-
assertEquals(expected, occurrences(xs))
65+
66+
assertEquals(expected, occurrences(data))
6367
}
6468
// -----------------------------------------------------------
6569

66-
// groupMapTo ------------------------------------------------
70+
// GroupMapGenGen --------------------------------------------
6771
@Test
68-
def anyCollectionGroupMapToFull(): Unit = {
72+
def anyCollectionGroupMapGenResultAs(): Unit = {
6973
def getUniqueUsersByCountrySorted(data: List[Record]): List[(String, List[String])] =
7074
data
71-
.groupMapTo(_.country)(_.user)
75+
.groupMapGenGen(_.country)(_.user)
7276
.collectValuesAs(SortedSet)
73-
.collectResultsAs(SortedMap)
74-
.result
77+
.resultAs(SortedMap)
7578
.view
7679
.mapValues(_.toList)
7780
.toList
@@ -95,15 +98,11 @@ final class TestIterableOnceExtensions {
9598
}
9699

97100
@Test
98-
def anyCollectionGroupByToFull(): Unit = {
99-
def getUniqueWordsByFirstLetterSorted(data: List[String]): List[(Char, List[String])] =
101+
def anyCollectionGroupMapGenGenReduce(): Unit = {
102+
def getAllWordsByFirstLetterSorted(data: List[String]): List[(Char, String)] =
100103
data
101-
.groupByTo(_.head)
102-
.collectValuesAs(SortedSet)
103-
.collectResultsAs(SortedMap)
104-
.result
105-
.view
106-
.mapValues(_.toList)
104+
.groupByGenGen(_.head)
105+
.reduceValuesAs(SortedMap)(_ ++ " " ++ _)
107106
.toList
108107

109108
val data = List(
@@ -116,23 +115,51 @@ final class TestIterableOnceExtensions {
116115
"Winter",
117116
"Banana"
118117
)
119-
120118
val expected = List(
121-
'A' -> List("Apple", "April", "Autumn"),
122-
'B' -> List("Banana"),
123-
'W' -> List("Wilson", "Winter")
119+
'A' -> "Autumn April Apple Apple",
120+
'B' -> "Banana Banana",
121+
'W' -> "Wilson Winter"
124122
)
125123

126-
assertEquals(expected, getUniqueWordsByFirstLetterSorted(data))
124+
assertEquals(expected, getAllWordsByFirstLetterSorted(data))
127125
}
128126

129127
@Test
130-
def anyCollectionGroupByToReduceFull(): Unit = {
131-
def getAllWordsByFirstLetterSorted(data: List[String]): List[(Char, String)] =
128+
def iterableOnceOpsGroupByGenSpecificFactory(): Unit = {
129+
def bitsByEven(data: BitSet): Map[Boolean, BitSet] =
130+
data.groupByGen(x => (x % 2) == 0).result
131+
132+
val data = BitSet(1, 2, 3, 4, 5)
133+
val expected = Map(
134+
true -> BitSet(2, 4),
135+
false -> BitSet(1, 3, 5)
136+
)
137+
138+
assertEquals(expected, bitsByEven(data))
139+
}
140+
141+
@Test
142+
def iterableOnceOpsGroupMapGenIterableFactory(): Unit = {
143+
def bitsByEvenAsChars(data: BitSet): Map[Boolean, Set[Char]] =
144+
data.groupMapGen(x => (x % 2) == 0)(_.toChar).result
145+
146+
val data = BitSet(100, 101, 102, 103, 104, 105)
147+
val expected = Map(
148+
true -> Set('d', 'f', 'h'),
149+
false -> Set('e', 'g', 'i')
150+
)
151+
152+
assertEquals(expected, bitsByEvenAsChars(data))
153+
}
154+
155+
@Test
156+
def iteratorGroupBy(): Unit = {
157+
def getUniqueWordsByFirstLetter(data: IterableOnce[String]): List[(Char, Set[String])] =
132158
data
133-
.groupByTo(_.head)
134-
.collectResultsAs(SortedMap)
135-
.reduce(_ ++ " " ++ _)
159+
.iterator
160+
.groupBy(_.head)
161+
.view
162+
.mapValues(_.toSet)
136163
.toList
137164

138165
val data = List(
@@ -147,12 +174,12 @@ final class TestIterableOnceExtensions {
147174
)
148175

149176
val expected = List(
150-
'A' -> "Autumn April Apple Apple",
151-
'B' -> "Banana Banana",
152-
'W' -> "Wilson Winter"
177+
'A' -> Set("Apple", "April", "Autumn"),
178+
'B' -> Set("Banana"),
179+
'W' -> Set("Wilson", "Winter")
153180
)
154181

155-
assertEquals(expected, getAllWordsByFirstLetterSorted(data))
182+
assertEquals(expected, getUniqueWordsByFirstLetter(data))
156183
}
157184
// -----------------------------------------------------------
158185
}

0 commit comments

Comments
 (0)