diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index ef7364a34d..707152ecbe 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -357,21 +357,110 @@ sealed abstract class Chain[+A] { /** * Groups elements inside this `Chain` according to the `Order` * of the keys produced by the given mapping function. + * + * {{{ + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.data.{Chain, NonEmptyChain} + * scala> import cats.implicits._ + * scala> val chain = Chain(12, -2, 3, -5) + * scala> val expectedResult = SortedMap(false -> NonEmptyChain(-2, -5), true -> NonEmptyChain(12, 3)) + * scala> val result = chain.groupBy(_ >= 0) + * scala> result === expectedResult + * res0: Boolean = true + * }}} */ - final def groupBy[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyChain[A]] = { - implicit val ordering: Ordering[B] = B.toOrdering - var m = SortedMap.empty[B, NonEmptyChain[A]] - val iter = iterator + final def groupBy[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyChain[A]] = + groupMap(key = f)(identity) + + /** + * Groups elements inside this `Chain` according to the `Order` + * of the keys produced by the given key function. + * And each element in a group is transformed into a value of type B + * using the mapping function. + * + * {{{ + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.data.{Chain, NonEmptyChain} + * scala> import cats.implicits._ + * scala> val chain = Chain(12, -2, 3, -5) + * scala> val expectedResult = SortedMap(false -> NonEmptyChain("-2", "-5"), true -> NonEmptyChain("12", "3")) + * scala> val result = chain.groupMap(_ >= 0)(_.toString) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupMap[K, B](key: A => K)(f: A => B)(implicit K: Order[K]): SortedMap[K, NonEmptyChain[B]] = { + implicit val ordering: Ordering[K] = K.toOrdering + var m = SortedMap.empty[K, NonEmptyChain[B]] + + for (elem <- iterator) { + val k = key(elem) + + m.get(k) match { + case Some(cat) => m = m.updated(key = k, value = cat :+ f(elem)) + case None => m += (k -> NonEmptyChain.one(f(elem))) + } + } + + m + } - while (iter.hasNext) { - val elem = iter.next() - val k = f(elem) + /** + * Groups elements inside this `Chain` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using their `Semigroup` + * + * {{{ + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.data.Chain + * scala> import cats.implicits._ + * scala> val chain = Chain("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = SortedMap("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = chain.groupMapReduce(_.trim.toLowerCase)(_ => 1) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupMapReduce[K, B](key: A => K)(f: A => B)(implicit K: Order[K], S: Semigroup[B]): SortedMap[K, B] = + groupMapReduceWith(key)(f)(S.combine) + + /** + * Groups elements inside this `Chain` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using the provided combine function. + * + * {{{ + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.data.Chain + * scala> import cats.implicits._ + * scala> val chain = Chain("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = SortedMap("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = chain.groupMapReduceWith(_.trim.toLowerCase)(_ => 1)(_ + _) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupMapReduceWith[K, B](key: A => K)(f: A => B)(combine: (B, B) => B)(implicit + K: Order[K] + ): SortedMap[K, B] = { + implicit val ordering: Ordering[K] = K.toOrdering + var m = SortedMap.empty[K, B] + + for (elem <- iterator) { + val k = key(elem) m.get(k) match { - case None => m += ((k, NonEmptyChain.one(elem))); () - case Some(cat) => m = m.updated(k, cat :+ elem) + case Some(b) => m = m.updated(key = k, value = combine(b, f(elem))) + case None => m += (k -> f(elem)) } } + m } diff --git a/core/src/main/scala/cats/data/NonEmptyChain.scala b/core/src/main/scala/cats/data/NonEmptyChain.scala index f137604ab2..4ef1f4a7e7 100644 --- a/core/src/main/scala/cats/data/NonEmptyChain.scala +++ b/core/src/main/scala/cats/data/NonEmptyChain.scala @@ -380,11 +380,162 @@ class NonEmptyChainOps[A](private val value: NonEmptyChain[A]) /** * Groups elements inside this `NonEmptyChain` according to the `Order` * of the keys produced by the given mapping function. + * + * {{{ + * scala> import cats.data.{NonEmptyChain, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nec = NonEmptyChain(12, -2, 3, -5) + * scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyChain(-2, -5), true -> NonEmptyChain(12, 3)) + * scala> val result = nec.groupBy(_ >= 0) + * scala> result === expectedResult + * res0: Boolean = true + * }}} */ final def groupBy[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NonEmptyChain[A]] = toChain.groupBy(f).asInstanceOf[NonEmptyMap[B, NonEmptyChain[A]]] - final def groupByNem[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NonEmptyChain[A]] = groupBy(f) + /** + * Groups elements inside this `NonEmptyChain` according to the `Order` + * of the keys produced by the given mapping function. + * + * {{{ + * scala> import cats.data.{NonEmptyChain, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nec = NonEmptyChain(12, -2, 3, -5) + * scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyChain(-2, -5), true -> NonEmptyChain(12, 3)) + * scala> val result = nec.groupByNem(_ >= 0) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupByNem[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NonEmptyChain[A]] = + groupBy(f) + + /** + * Groups elements inside this `NonEmptyChain` according to the `Order` + * of the keys produced by the given key function. + * And each element in a group is transformed into a value of type B + * using the mapping function. + * + * {{{ + * scala> import cats.data.{NonEmptyChain, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nec = NonEmptyChain(12, -2, 3, -5) + * scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyChain("-2", "-5"), true -> NonEmptyChain("12", "3")) + * scala> val result = nec.groupMap(_ >= 0)(_.toString) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupMap[K, B](key: A => K)(f: A => B)(implicit K: Order[K]): NonEmptyMap[K, NonEmptyChain[B]] = + toChain.groupMap(key)(f).asInstanceOf[NonEmptyMap[K, NonEmptyChain[B]]] + + /** + * Groups elements inside this `NonEmptyChain` according to the `Order` + * of the keys produced by the given key function. + * And each element in a group is transformed into a value of type B + * using the mapping function. + * + * {{{ + * scala> import cats.data.{NonEmptyChain, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nec = NonEmptyChain(12, -2, 3, -5) + * scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyChain("-2", "-5"), true -> NonEmptyChain("12", "3")) + * scala> val result = nec.groupMapNem(_ >= 0)(_.toString) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupMapNem[K, B](key: A => K)(f: A => B)(implicit K: Order[K]): NonEmptyMap[K, NonEmptyChain[B]] = + groupMap(key)(f) + + /** + * Groups elements inside this `NonEmptyChain` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using their `Semigroup` + * + * {{{ + * scala> import cats.data.{NonEmptyChain, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nec = NonEmptyChain("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = nec.groupMapReduce(_.trim.toLowerCase)(_ => 1) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupMapReduce[K, B](key: A => K)(f: A => B)(implicit K: Order[K], B: Semigroup[B]): NonEmptyMap[K, B] = + toChain.groupMapReduce(key)(f).asInstanceOf[NonEmptyMap[K, B]] + + /** + * Groups elements inside this `NonEmptyChain` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using their `Semigroup` + * + * {{{ + * scala> import cats.data.{NonEmptyChain, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nec = NonEmptyChain("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = nec.groupMapReduceNem(_.trim.toLowerCase)(_ => 1) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupMapReduceNem[K, B](key: A => K)(f: A => B)(implicit K: Order[K], B: Semigroup[B]): NonEmptyMap[K, B] = + groupMapReduce(key)(f) + + /** + * Groups elements inside this `NonEmptyChain` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using the provided combine function. + * + * {{{ + * scala> import cats.data.{NonEmptyChain, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nec = NonEmptyChain("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = nec.groupMapReduceWith(_.trim.toLowerCase)(_ => 1)(_ + _) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupMapReduceWith[K, B](key: A => K)(f: A => B)(combine: (B, B) => B)(implicit + K: Order[K] + ): NonEmptyMap[K, B] = + toChain.groupMapReduceWith(key)(f)(combine).asInstanceOf[NonEmptyMap[K, B]] + + /** + * Groups elements inside this `NonEmptyChain` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using the provided combine function. + * + * {{{ + * scala> import cats.data.{NonEmptyChain, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nec = NonEmptyChain("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = nec.groupMapReduceWithNem(_.trim.toLowerCase)(_ => 1)(_ + _) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupMapReduceWithNem[K, B](key: A => K)(f: A => B)(combine: (B, B) => B)(implicit + K: Order[K] + ): NonEmptyMap[K, B] = + groupMapReduceWith(key)(f)(combine) final def iterator: Iterator[A] = toChain.iterator diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index fccc9ccc7a..dc7d872750 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -402,40 +402,180 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) extends NonEmptyCollec * res0: Boolean = true * }}} */ - def groupBy[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyList[A]] = { - implicit val ordering: Ordering[B] = B.toOrdering - var m = TreeMap.empty[B, mutable.Builder[A, List[A]]] + def groupBy[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyList[A]] = + groupMap(key = f)(identity) - for { elem <- toList } { - val k = f(elem) + /** + * Groups elements inside this `NonEmptyList` according to the `Order` + * of the keys produced by the given mapping function. + * + * {{{ + * scala> import cats.data.{NonEmptyList, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nel = NonEmptyList.of(12, -2, 3, -5) + * scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyList.of(-2, -5), true -> NonEmptyList.of(12, 3)) + * scala> val result = nel.groupByNem(_ >= 0) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + def groupByNem[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NonEmptyList[A]] = + NonEmptyMap.fromMapUnsafe(groupBy(f)) + + /** + * Groups elements inside this `NonEmptyList` according to the `Order` + * of the keys produced by the given key function. + * And each element in a group is transformed into a value of type B + * using the mapping function. + * + * {{{ + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.data.NonEmptyList + * scala> import cats.implicits._ + * scala> val nel = NonEmptyList.of(12, -2, 3, -5) + * scala> val expectedResult = SortedMap(false -> NonEmptyList.of("-2", "-5"), true -> NonEmptyList.of("12", "3")) + * scala> val result = nel.groupMap(_ >= 0)(_.toString) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + def groupMap[K, B](key: A => K)(f: A => B)(implicit K: Order[K]): SortedMap[K, NonEmptyList[B]] = { + implicit val ordering: Ordering[K] = K.toOrdering + var m = TreeMap.empty[K, mutable.Builder[B, List[B]]] + + for (elem <- toList) { + val k = key(elem) m.get(k) match { - case None => m += ((k, List.newBuilder[A] += elem)) - case Some(builder) => builder += elem + case Some(builder) => builder += f(elem) + case None => m += (k -> (List.newBuilder[B] += f(elem))) } } m.map { case (k, v) => (k, NonEmptyList.fromListUnsafe(v.result())) - }: TreeMap[B, NonEmptyList[A]] + } } /** * Groups elements inside this `NonEmptyList` according to the `Order` - * of the keys produced by the given mapping function. + * of the keys produced by the given key function. + * And each element in a group is transformed into a value of type B + * using the mapping function. * * {{{ * scala> import cats.data.{NonEmptyList, NonEmptyMap} * scala> import cats.implicits._ * scala> val nel = NonEmptyList.of(12, -2, 3, -5) - * scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyList.of(-2, -5), true -> NonEmptyList.of(12, 3)) - * scala> val result = nel.groupByNem(_ >= 0) + * scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyList.of("-2", "-5"), true -> NonEmptyList.of("12", "3")) + * scala> val result = nel.groupMapNem(_ >= 0)(_.toString) * scala> result === expectedResult * res0: Boolean = true * }}} */ - def groupByNem[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NonEmptyList[A]] = - NonEmptyMap.fromMapUnsafe(groupBy(f)) + def groupMapNem[K, B](key: A => K)(f: A => B)(implicit K: Order[K]): NonEmptyMap[K, NonEmptyList[B]] = + NonEmptyMap.fromMapUnsafe(groupMap(key)(f)) + + /** + * Groups elements inside this `NonEmptyList` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using their `Semigroup`. + * + * {{{ + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.data.NonEmptyList + * scala> import cats.implicits._ + * scala> val nel = NonEmptyList.of("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = SortedMap("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = nel.groupMapReduce(_.trim.toLowerCase)(_ => 1) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + def groupMapReduce[K, B](key: A => K)(f: A => B)(implicit K: Order[K], B: Semigroup[B]): SortedMap[K, B] = + groupMapReduceWith(key)(f)(B.combine) + + /** + * Groups elements inside this `NonEmptyList` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using their `Semigroup`. + * + * {{{ + * scala> import cats.data.{NonEmptyList, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nel = NonEmptyList.of("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = nel.groupMapReduceNem(_.trim.toLowerCase)(_ => 1) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + def groupMapReduceNem[K, B](key: A => K)(f: A => B)(implicit K: Order[K], B: Semigroup[B]): NonEmptyMap[K, B] = + NonEmptyMap.fromMapUnsafe(groupMapReduce(key)(f)) + + /** + * Groups elements inside this `NonEmptyList` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using the provided combine function. + * + * {{{ + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.data.NonEmptyList + * scala> import cats.implicits._ + * scala> val nel = NonEmptyList.of("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = SortedMap("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = nel.groupMapReduceWith(_.trim.toLowerCase)(_ => 1)(_ + _) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + def groupMapReduceWith[K, B](key: A => K)(f: A => B)(combine: (B, B) => B)(implicit K: Order[K]): SortedMap[K, B] = { + implicit val ordering: Ordering[K] = K.toOrdering + var m = TreeMap.empty[K, B] + + for (elem <- toList) { + val k = key(elem) + + m.get(k) match { + case Some(b) => m = m.updated(key = k, value = combine(b, f(elem))) + case None => m += (k -> f(elem)) + } + } + + m + } + + /** + * Groups elements inside this `NonEmptyList` according to the `Order` + * of the keys produced by the given key function. + * Then each element in a group is transformed into a value of type B + * using the mapping function. + * And finally they are all reduced into a single value + * using the provided combine function. + * + * {{{ + * scala> import cats.data.{NonEmptyList, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nel = NonEmptyList.of("Hello", "World", "Goodbye", "World") + * scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2) + * scala> val result = nel.groupMapReduceWithNem(_.trim.toLowerCase)(_ => 1)(_ + _) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + def groupMapReduceWithNem[K, B](key: A => K)(f: A => B)(combine: (B, B) => B)(implicit + K: Order[K] + ): NonEmptyMap[K, B] = + NonEmptyMap.fromMapUnsafe(groupMapReduceWith(key)(f)(combine)) /** * Creates new `NonEmptyMap`, similarly to List#toMap from scala standard library. diff --git a/tests/src/test/scala/cats/tests/ChainSuite.scala b/tests/src/test/scala/cats/tests/ChainSuite.scala index f082236971..468fda49db 100644 --- a/tests/src/test/scala/cats/tests/ChainSuite.scala +++ b/tests/src/test/scala/cats/tests/ChainSuite.scala @@ -4,7 +4,7 @@ import cats.{Align, Alternative, CoflatMap, Monad, Show, Traverse, TraverseFilte import cats.data.Chain import cats.data.Chain.==: import cats.data.Chain.`:==` -import cats.kernel.{Eq, Hash, Monoid, Order, PartialOrder} +import cats.kernel.{Eq, Hash, Monoid, Order, PartialOrder, Semigroup} import cats.kernel.laws.discipline.{EqTests, HashTests, MonoidTests, OrderTests, PartialOrderTests} import cats.laws.discipline.{ AlignTests, @@ -166,8 +166,34 @@ class ChainSuite extends CatsSuite { } test("groupBy consistent with List#groupBy") { - forAll { (cs: Chain[String], f: String => Int) => - assert(cs.groupBy(f).map { case (k, v) => (k, v.toList) }.toMap === (cs.toList.groupBy(f).toMap)) + forAll { (cs: Chain[String], key: String => Int) => + val result = cs.groupBy(key).map { case (k, v) => (k, v.toList) }.toMap + val expected = cs.toList.groupBy(key).toMap + assert(result === expected) + } + } + + test("groupMap consistent with List#groupBy + Map#mapValues") { + forAll { (cs: Chain[String], key: String => String, f: String => Int) => + val result = cs.groupMap(key)(f).map { case (k, v) => (k, v.toList) }.toMap + val expected = cs.toList.groupBy(key).map { case (k, v) => (k, v.map(f)) } + assert(result === expected) + } + } + + test("groupMapReduce consistent with List#groupBy + Map#mapValues + List#reduce") { + forAll { (cs: Chain[String], key: String => String, f: String => Int) => + val result = cs.groupMapReduce(key)(f).toMap + val expected = cs.toList.groupBy(key).map { case (k, v) => (k, v.map(f).reduce(Semigroup[Int].combine)) } + assert(result === expected) + } + } + + test("groupMapReduceWith consistent with List#groupBy + Map#mapValues + List#reduce") { + forAll { (cs: Chain[String], key: String => String, f: String => Int, combine: (Int, Int) => Int) => + val result = cs.groupMapReduceWith(key)(f)(combine).toMap + val expected = cs.toList.groupBy(key).map { case (k, v) => (k, v.map(f).reduce(combine)) } + assert(result === expected) } } diff --git a/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala b/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala index 6b61fea50c..6d84a0435e 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala @@ -141,9 +141,35 @@ class NonEmptyChainSuite extends NonEmptyCollectionSuite[Chain, NonEmptyChain, N } } - test("groupBy consistent with List#groupBy") { - forAll { (cs: NonEmptyChain[String], f: String => Int) => - assert(cs.groupBy(f).map(_.toNonEmptyList) === (cs.toNonEmptyList.groupByNem(f))) + test("groupBy consistent with NonEmptyList#groupByNem") { + forAll { (cs: NonEmptyChain[String], key: String => Int) => + val result = cs.groupBy(key).map(_.toNonEmptyList) + val expected = cs.toNonEmptyList.groupByNem(key) + assert(result === expected) + } + } + + test("groupMap consistent with NonEmptyList#groupMapNem") { + forAll { (cs: NonEmptyChain[String], key: String => String, f: String => Int) => + val result = cs.groupMap(key)(f).map(_.toNonEmptyList) + val expected = cs.toNonEmptyList.groupMapNem(key)(f) + assert(result === expected) + } + } + + test("groupMapReduce consistent with NonEmptyList#groupMapReduceNem") { + forAll { (cs: NonEmptyChain[String], key: String => String, f: String => Int) => + val result = cs.groupMapReduce(key)(f) + val expected = cs.toNonEmptyList.groupMapReduceNem(key)(f) + assert(result === expected) + } + } + + test("groupMapReduceWith consistent with NonEmptyList#groupMapReduceWithNem") { + forAll { (cs: NonEmptyChain[String], key: String => String, f: String => Int, combine: (Int, Int) => Int) => + val result = cs.groupMapReduceWith(key)(f)(combine) + val expected = cs.toNonEmptyList.groupMapReduceWithNem(key)(f)(combine) + assert(result === expected) } } diff --git a/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala b/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala index 269e12ff8e..80efe31f40 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala @@ -322,8 +322,34 @@ class NonEmptyListSuite extends NonEmptyCollectionSuite[List, NonEmptyList, NonE } test("NonEmptyList#groupBy is consistent with List#groupBy") { - forAll { (nel: NonEmptyList[Int], f: Int => Int) => - assert((nel.groupBy(f).map { case (k, v) => (k, v.toList) }: Map[Int, List[Int]]) === (nel.toList.groupBy(f))) + forAll { (nel: NonEmptyList[Int], key: Int => Int) => + val result = nel.groupBy(key).map { case (k, v) => (k, v.toList) }.toMap + val expected = nel.toList.groupBy(key) + assert(result === expected) + } + } + + test("NonEmptyList#groupMap is consistent with List#groupBy + Map#mapValues") { + forAll { (nel: NonEmptyList[Int], key: Int => Int, f: Int => String) => + val result = nel.groupMap(key)(f).map { case (k, v) => (k, v.toList) }.toMap + val expected = nel.toList.groupBy(key).map { case (k, v) => (k, v.map(f)) } + assert(result === expected) + } + } + + test("NonEmptyList#groupMapReduce is consistent with List#groupBy + Map#mapValues + List#reduce") { + forAll { (nel: NonEmptyList[Int], key: Int => Int, f: Int => String) => + val result = nel.groupMapReduce(key)(f).toMap + val expected = nel.toList.groupBy(key).map { case (k, v) => (k, v.map(f).reduce(Semigroup[String].combine)) } + assert(result === expected) + } + } + + test("NonEmptyList#groupMapReduceWith is consistent with List#groupBy + Map#mapValues + List#reduce") { + forAll { (nel: NonEmptyList[Int], key: Int => Int, f: Int => String, combine: (String, String) => String) => + val result = nel.groupMapReduceWith(key)(f)(combine).toMap + val expected = nel.toList.groupBy(key).map { case (k, v) => (k, v.map(f).reduce(combine)) } + assert(result === expected) } }