Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PoC: Add groupMapTo fluent API #53

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ package next
private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C](
private val col: IterableOnceOps[A, CC, C]
) extends AnyVal {
import NextIterableOnceOpsExtensions.{GroupMapToView, GroupMapView}

def groupBy[K](key: A => K)(implicit groupsFactory: Factory[A, C]): immutable.Map[K, C] =
viewGroupByTo(key).toMap

def viewGroupByTo[K](key: A => K)(implicit groupsFactory: Factory[A, C]): GroupMapToView[A, K, A, C] =
viewGroupBy(key).collectGroupsTo(groupsFactory)

def viewGroupBy[K](key: A => K): GroupMapView[A, K, A] =
viewGroupMap(key)(identity)

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 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 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
* have the same discriminator are then transformed by the `value` function and then reduced into a
Expand All @@ -28,14 +48,51 @@ 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, V](key: A => K)(f: A => V)(reduce: (V, V) => V): immutable.Map[K, V] =
viewGroupMap(key)(f).reduceValuesTo(immutable.Map)(reduce)
}

private[next] object NextIterableOnceOpsExtensions {
final class GroupMapView[A, K, V] private[NextIterableOnceOpsExtensions](
col: IterableOnceOps[A, AnyConstr, _],
key: A => K,
f: A => V
) {
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)) {
case Some(b) => Some(reduce(b, f(elem)))
case None => Some(f(elem))
}
}
resultFactory.fromSpecific(m)
}

def collectGroupsTo[C](groupsFactory: Factory[V, C]): GroupMapToView[A, K, V, C] =
new GroupMapToView(col, key, f, groupsFactory)
}

final class GroupMapToView[A, K, V, C] private[NextIterableOnceOpsExtensions](
col: IterableOnceOps[A, AnyConstr, _],
key: A => K,
f: A => V,
groupsFactory: Factory[V, C]
) {
def toMap: immutable.Map[K, C] =
to(immutable.Map)

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 = groupsFactory.newBuilder.addOne(v))
}
}
resultFactory.fromSpecific(m.view.mapValues(_.result()))
}
m.to(immutable.Map)
}
}
159 changes: 142 additions & 17 deletions src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,49 +16,172 @@ import org.junit.Assert._
import org.junit.Test
import scala.collection.IterableOnceOps
import scala.collection.generic.IsIterableOnce
import scala.collection.immutable.{ArraySeq, BitSet, SortedMap, SortedSet}

final class TestIterableOnceExtensions {
import TestIterableOnceExtensions.LowerCaseString
import 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))
}
// -----------------------------------------------------------

// GroupMapGenGen --------------------------------------------
@Test
def anyCollectionGroupMapToViewTo(): Unit = {
def getUniqueUsersByCountrySorted(data: List[Record]): List[(String, List[String])] =
data
.viewGroupMap(_.country)(_.user)
.collectGroupsTo(SortedSet)
.to(SortedMap)
.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 anyCollectionGroupMapViewReduceValuesTo(): Unit = {
def getAllWordsByFirstLetterSorted(data: List[String]): List[(Char, String)] =
data
.viewGroupBy(_.head)
.reduceValuesTo(SortedMap)(_ ++ " " ++ _)
.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))
}

@Test
def iterableOnceOpsViewGroupByToSpecificFactoryToMap(): Unit = {
def bitsByEven(data: BitSet): Map[Boolean, BitSet] =
data.viewGroupByTo(x => (x % 2) == 0).toMap

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 iterableOnceOpsViewGroupMapToIterableFactoryToMap(): Unit = {
def bitsByEvenAsChars(data: BitSet): Map[Boolean, Set[Char]] =
data.viewGroupMapTo(x => (x % 2) == 0)(_.toChar).toMap

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
.iterator
.groupBy(_.head)
.view
.mapValues(_.toSet)
.toList

val data = List(
"Autumn",
"Banana",
"April",
"Wilson",
"Apple",
"Apple",
"Winter",
"Banana"
)

val expected = List(
'A' -> Set("Apple", "April", "Autumn"),
'B' -> Set("Banana"),
'W' -> Set("Wilson", "Winter")
)

assertEquals(expected, getUniqueWordsByFirstLetter(data))
}
// -----------------------------------------------------------
}

object TestIterableOnceExtensions {
Expand All @@ -81,4 +204,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)
}