diff --git a/core/src/main/scala/cats/syntax/list.scala b/core/src/main/scala/cats/syntax/list.scala index 0891a2f1bf..fee7388ca1 100644 --- a/core/src/main/scala/cats/syntax/list.scala +++ b/core/src/main/scala/cats/syntax/list.scala @@ -2,7 +2,6 @@ package cats package syntax import cats.data.{NonEmptyChain, NonEmptyList} - import scala.collection.immutable.SortedMap trait ListSyntax { @@ -52,6 +51,32 @@ final class ListOps[A](private val la: List[A]) extends AnyVal { toNel.fold(SortedMap.empty[B, NonEmptyList[A]])(_.groupBy(f)) } + /** + * Groups elements inside this `List` according to the `Order` of the keys + * produced by the given mapping monadic function. + * + * {{{ + * scala> import cats.data.NonEmptyList + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.implicits._ + * + * scala> val list = List(12, -2, 3, -5) + * + * scala> val expectedResult = Option(SortedMap(false -> NonEmptyList.of(-2, -5), true -> NonEmptyList.of(12, 3))) + * + * scala> list.groupByNelA(num => Option(0).map(num >= _)) === expectedResult + * res0: Boolean = true + * }}} + */ + def groupByNelA[F[_], B](f: A => F[B])(implicit F: Applicative[F], B: Order[B]): F[SortedMap[B, NonEmptyList[A]]] = { + implicit val ordering: Ordering[B] = B.toOrdering + val functor = Functor[SortedMap[B, *]] + + toNel.fold(F.pure(SortedMap.empty[B, NonEmptyList[A]]))(nel => + F.map(nel.traverse(a => F.tupleLeft(f(a), a)))(list => functor.map(list.groupBy(_._2))(_.map(_._1))) + ) + } + /** Produces a `NonEmptyList` containing cumulative results of applying the * operator going left to right. * diff --git a/tests/src/test/scala/cats/tests/ListSuite.scala b/tests/src/test/scala/cats/tests/ListSuite.scala index 5124f6880d..009ef5085e 100644 --- a/tests/src/test/scala/cats/tests/ListSuite.scala +++ b/tests/src/test/scala/cats/tests/ListSuite.scala @@ -62,6 +62,12 @@ class ListSuite extends CatsSuite { } ) + test("groupByNelA should be consistent with groupByNel")( + forAll { (fa: List[Int], f: Int => Int) => + fa.groupByNelA(f.andThen(Option(_))) should ===(Option(fa.groupByNel(f))) + } + ) + test("show") { List(1, 2, 3).show should ===("List(1, 2, 3)") (Nil: List[Int]).show should ===("List()")