From 7f83f63250279382946bd26acfe0d1a095d92256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 25 Oct 2020 11:22:40 -0500 Subject: [PATCH 1/4] Adding groupMapReduce method to Iterator --- .../scala/collection/IteratorExtensions.scala | 40 +++++++++++++++++++ .../collection/TestIteratorExtensions.scala | 29 ++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/main/scala/scala/collection/IteratorExtensions.scala create mode 100644 src/test/scala/scala/collection/TestIteratorExtensions.scala diff --git a/src/main/scala/scala/collection/IteratorExtensions.scala b/src/main/scala/scala/collection/IteratorExtensions.scala new file mode 100644 index 0000000..18bf8ee --- /dev/null +++ b/src/main/scala/scala/collection/IteratorExtensions.scala @@ -0,0 +1,40 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.collection + +object IteratorExtensions { + implicit class IteratorExtensionsOps[A](private val iter: Iterator[A]) extends AnyVal { + /** + * Partitions this Iterator 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 + * single value with the `reduce` function. + * + * {{{ + * def occurrences[A](as: Iterator[A]): Map[A, Int] = + * as.groupMapReduce(identity)(_ => 1)(_ + _) + * }}} + * + * @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] + iter.foreach { elem => + m.updateWith(key = key(elem)) { + case Some(b) => Some(reduce(b, f(elem))) + case None => Some(f(elem)) + } + } + m.to(immutable.Map) + } + } +} diff --git a/src/test/scala/scala/collection/TestIteratorExtensions.scala b/src/test/scala/scala/collection/TestIteratorExtensions.scala new file mode 100644 index 0000000..1da706d --- /dev/null +++ b/src/test/scala/scala/collection/TestIteratorExtensions.scala @@ -0,0 +1,29 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.collection + +import org.junit.Assert._ +import org.junit.Test +import IteratorExtensions._ + +final class TestIteratorExtensions { + @Test + def groupMapReduce(): Unit = { + def occurrences[A](as: Seq[A]): Map[A, Int] = + as.iterator.groupMapReduce(identity)(_ => 1)(_ + _) + + val xs = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b') + val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) + assertEquals(expected, occurrences(xs)) + } +} From ecf7fcc4d52543f1547e37167d5a431bb91a6c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 26 Oct 2020 12:06:40 -0500 Subject: [PATCH 2/4] Following structure and naming conventions --- .../scala/scala/collection/{ => next}/IteratorExtensions.scala | 3 ++- .../scala/collection/{ => next}/TestIteratorExtensions.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename src/main/scala/scala/collection/{ => next}/IteratorExtensions.scala (91%) rename src/test/scala/scala/collection/{ => next}/TestIteratorExtensions.scala (95%) diff --git a/src/main/scala/scala/collection/IteratorExtensions.scala b/src/main/scala/scala/collection/next/IteratorExtensions.scala similarity index 91% rename from src/main/scala/scala/collection/IteratorExtensions.scala rename to src/main/scala/scala/collection/next/IteratorExtensions.scala index 18bf8ee..c2b2a7d 100644 --- a/src/main/scala/scala/collection/IteratorExtensions.scala +++ b/src/main/scala/scala/collection/next/IteratorExtensions.scala @@ -11,9 +11,10 @@ */ package scala.collection +package next object IteratorExtensions { - implicit class IteratorExtensionsOps[A](private val iter: Iterator[A]) extends AnyVal { + implicit class NextIteratorExtensions[A](private val iter: Iterator[A]) extends AnyVal { /** * Partitions this Iterator 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 diff --git a/src/test/scala/scala/collection/TestIteratorExtensions.scala b/src/test/scala/scala/collection/next/TestIteratorExtensions.scala similarity index 95% rename from src/test/scala/scala/collection/TestIteratorExtensions.scala rename to src/test/scala/scala/collection/next/TestIteratorExtensions.scala index 1da706d..0cd7306 100644 --- a/src/test/scala/scala/collection/TestIteratorExtensions.scala +++ b/src/test/scala/scala/collection/next/TestIteratorExtensions.scala @@ -10,7 +10,7 @@ * additional information regarding copyright ownership. */ -package scala.collection +package scala.collection.next import org.junit.Assert._ import org.junit.Test From f5368594572423ce26947a19d3bc164ad7b55931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 26 Oct 2020 13:10:10 -0500 Subject: [PATCH 3/4] Adding the extension method to any IterableOnce instead to just Iterators --- ...ons.scala => IterableOnceExtensions.scala} | 10 +-- .../next/TestIterableOnceExtensions.scala | 86 +++++++++++++++++++ .../next/TestIteratorExtensions.scala | 29 ------- 3 files changed, 91 insertions(+), 34 deletions(-) rename src/main/scala/scala/collection/next/{IteratorExtensions.scala => IterableOnceExtensions.scala} (71%) create mode 100644 src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala delete mode 100644 src/test/scala/scala/collection/next/TestIteratorExtensions.scala diff --git a/src/main/scala/scala/collection/next/IteratorExtensions.scala b/src/main/scala/scala/collection/next/IterableOnceExtensions.scala similarity index 71% rename from src/main/scala/scala/collection/next/IteratorExtensions.scala rename to src/main/scala/scala/collection/next/IterableOnceExtensions.scala index c2b2a7d..e90ae0e 100644 --- a/src/main/scala/scala/collection/next/IteratorExtensions.scala +++ b/src/main/scala/scala/collection/next/IterableOnceExtensions.scala @@ -13,16 +13,16 @@ package scala.collection package next -object IteratorExtensions { - implicit class NextIteratorExtensions[A](private val iter: Iterator[A]) extends AnyVal { +object IterableOnceExtensions { + implicit class NextIterableOnceOpsExtensions[A](private val iter: IterableOnceOps[A, Any, Any]) extends AnyVal { /** - * Partitions this Iterator into a map according to a discriminator function `key`. All the values that + * 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 * single value with the `reduce` function. * * {{{ - * def occurrences[A](as: Iterator[A]): Map[A, Int] = - * as.groupMapReduce(identity)(_ => 1)(_ + _) + * def occurrences[A](as: IterableOnce[A]): Map[A, Int] = + * as.iterator.groupMapReduce(identity)(_ => 1)(_ + _) * }}} * * @note This will force the evaluation of the Iterator. diff --git a/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala new file mode 100644 index 0000000..f38c595 --- /dev/null +++ b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala @@ -0,0 +1,86 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.collection.next + +import org.junit.Assert._ +import org.junit.Test +import scala.collection.IterableOnceOps +import scala.collection.generic.IsIterableOnce + +import IterableOnceExtensions._ + +final class TestIterableOnceExtensions { + import TestIterableOnceExtensions.LowerCaseString + + @Test + def iteratorGroupMapReduce(): Unit = { + def occurrences[A](coll: IterableOnce[A]): Map[A, Int] = + coll.iterator.groupMapReduce(identity)(_ => 1)(_ + _) + + val xs = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b') + val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) + assertEquals(expected, occurrences(xs)) + } + + @Test + def iterableOnceOpsGroupMapReduce(): Unit = { + def occurrences[A](coll: IterableOnceOps[A, Any, Any]): Map[A, Int] = + coll.groupMapReduce(identity)(_ => 1)(_ + _) + + val xs = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b') + val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) + assertEquals(expected, occurrences(xs)) + } + + @Test + def anyLikeIterableOnceGroupMapReduce(): Unit = { + def occurrences[Repr](coll: Repr)(implicit it: IsIterableOnce[Repr]): Map[it.A, Int] = + it(coll).iterator.groupMapReduce(identity)(_ => 1)(_ + _) + + val xs = "abbcaaab" + val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) + assertEquals(expected, occurrences(xs)) + } + + @Test + def customIterableOnceOpsGroupMapReduce(): Unit = { + def occurrences(coll: LowerCaseString): Map[Char, Int] = + coll.groupMapReduce(identity)(_ => 1)(_ + _) + + val xs = LowerCaseString("abBcAaAb") + val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) + assertEquals(expected, occurrences(xs)) + } +} + +object TestIterableOnceExtensions { + final case class LowerCaseString(source: String) extends IterableOnce[Char] with IterableOnceOps[Char, Iterable, String] { + override def iterator: Iterator[Char] = source.iterator.map(_.toLower) + + override def scanLeft[B](z: B)(op: (B, Char) => B): Iterable[B] = ??? + override def filter(p: Char => Boolean): String = ??? + override def filterNot(pred: Char => Boolean): String = ??? + override def take(n: Int): String = ??? + override def takeWhile(p: Char => Boolean): String = ??? + override def drop(n: Int): String = ??? + override def dropWhile(p: Char => Boolean): String = ??? + override def slice(from: Int, until: Int): String = ??? + override def map[B](f: Char => B): Iterable[B] = ??? + override def flatMap[B](f: Char => IterableOnce[B]): Iterable[B] = ??? + override def flatten[B](implicit asIterable: Char => IterableOnce[B]): Iterable[B] = ??? + override def collect[B](pf: PartialFunction[Char,B]): Iterable[B] = ??? + override def zipWithIndex: Iterable[(Char, Int)] = ??? + override def span(p: Char => Boolean): (String, String) = ??? + override def tapEach[U](f: Char => U): String = ??? + } +} diff --git a/src/test/scala/scala/collection/next/TestIteratorExtensions.scala b/src/test/scala/scala/collection/next/TestIteratorExtensions.scala deleted file mode 100644 index 0cd7306..0000000 --- a/src/test/scala/scala/collection/next/TestIteratorExtensions.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala.collection.next - -import org.junit.Assert._ -import org.junit.Test -import IteratorExtensions._ - -final class TestIteratorExtensions { - @Test - def groupMapReduce(): Unit = { - def occurrences[A](as: Seq[A]): Map[A, Int] = - as.iterator.groupMapReduce(identity)(_ => 1)(_ + _) - - val xs = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b') - val expected = Map('a' -> 4, 'b' -> 3, 'c' -> 1) - assertEquals(expected, occurrences(xs)) - } -} From 4295a1315f941ff476d31d640ee10c886f24de13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sat, 28 Nov 2020 15:40:25 -0500 Subject: [PATCH 4/4] Following current style & structure proposals --- .../next/IterableOnceExtensions.scala | 41 ------------------- .../next/NextIterableOnceOpsExtensions.scala | 41 +++++++++++++++++++ .../scala/scala/collection/next/package.scala | 22 ++++++++++ .../next/TestIterableOnceExtensions.scala | 4 +- 4 files changed, 64 insertions(+), 44 deletions(-) delete mode 100644 src/main/scala/scala/collection/next/IterableOnceExtensions.scala create mode 100644 src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala create mode 100644 src/main/scala/scala/collection/next/package.scala diff --git a/src/main/scala/scala/collection/next/IterableOnceExtensions.scala b/src/main/scala/scala/collection/next/IterableOnceExtensions.scala deleted file mode 100644 index e90ae0e..0000000 --- a/src/main/scala/scala/collection/next/IterableOnceExtensions.scala +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala.collection -package next - -object IterableOnceExtensions { - implicit class NextIterableOnceOpsExtensions[A](private val iter: IterableOnceOps[A, Any, Any]) extends AnyVal { - /** - * 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 - * single value with the `reduce` function. - * - * {{{ - * def occurrences[A](as: IterableOnce[A]): Map[A, Int] = - * as.iterator.groupMapReduce(identity)(_ => 1)(_ + _) - * }}} - * - * @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] - iter.foreach { elem => - m.updateWith(key = key(elem)) { - case Some(b) => Some(reduce(b, f(elem))) - case None => Some(f(elem)) - } - } - m.to(immutable.Map) - } - } -} diff --git a/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala new file mode 100644 index 0000000..2bd687d --- /dev/null +++ b/src/main/scala/scala/collection/next/NextIterableOnceOpsExtensions.scala @@ -0,0 +1,41 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.collection +package next + +private[next] final class NextIterableOnceOpsExtensions[A, CC[_], C]( + private val col: IterableOnceOps[A, CC, C] +) extends AnyVal { + /** + * 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 + * single value with the `reduce` function. + * + * {{{ + * def occurrences[A](as: IterableOnce[A]): Map[A, Int] = + * as.iterator.groupMapReduce(identity)(_ => 1)(_ + _) + * }}} + * + * @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)) + } + } + m.to(immutable.Map) + } +} diff --git a/src/main/scala/scala/collection/next/package.scala b/src/main/scala/scala/collection/next/package.scala new file mode 100644 index 0000000..db9e1a6 --- /dev/null +++ b/src/main/scala/scala/collection/next/package.scala @@ -0,0 +1,22 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.collection + +import scala.language.implicitConversions + +package object next { + implicit final def scalaNextSyntaxForIterableOnceOps[A, CC[_], C]( + col: IterableOnceOps[A, CC, C] + ): NextIterableOnceOpsExtensions[A, CC, C] = + new NextIterableOnceOpsExtensions(col) +} diff --git a/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala index f38c595..0d8962a 100644 --- a/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala +++ b/src/test/scala/scala/collection/next/TestIterableOnceExtensions.scala @@ -17,8 +17,6 @@ import org.junit.Test import scala.collection.IterableOnceOps import scala.collection.generic.IsIterableOnce -import IterableOnceExtensions._ - final class TestIterableOnceExtensions { import TestIterableOnceExtensions.LowerCaseString @@ -34,7 +32,7 @@ final class TestIterableOnceExtensions { @Test def iterableOnceOpsGroupMapReduce(): Unit = { - def occurrences[A](coll: IterableOnceOps[A, Any, Any]): Map[A, Int] = + def occurrences[A, CC[_], C](coll: IterableOnceOps[A, CC, C]): Map[A, Int] = coll.groupMapReduce(identity)(_ => 1)(_ + _) val xs = Seq('a', 'b', 'b', 'c', 'a', 'a', 'a', 'b')