Skip to content
Open
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
157 changes: 157 additions & 0 deletions core/src/main/scala/cats/data/NonEmptySeq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,163 @@ final class NonEmptySeq[+A] private (val toSeq: Seq[A]) extends AnyVal with NonE
final def groupByNem[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NonEmptySeq[A]] =
NonEmptyMap.fromMapUnsafe(groupBy(f))

/**
* Groups elements inside this `NonEmptySeq` 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.NonEmptySeq
* scala> import cats.syntax.all._
* scala> val neSeq = NonEmptySeq.of(12, -2, 3, -5)
* scala> val expectedResult = SortedMap(false -> NonEmptySeq.of("-2", "-5"), true -> NonEmptySeq.of("12", "3"))
* scala> val result = neSeq.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, NonEmptySeq[B]] = {
implicit val ordering: Ordering[K] = K.toOrdering
var m = TreeMap.empty[K, mutable.Builder[B, Seq[B]]]

for { elem <- toSeq } {
val k = key(elem)

m.get(k) match {
case None => m += ((k, Seq.newBuilder[B] += f(elem)))
case Some(builder) => builder += f(elem)
}
}

m.map { case (k, v) =>
(k, NonEmptySeq.fromSeqUnsafe(v.result()))
}: TreeMap[K, NonEmptySeq[B]]
}
Comment on lines +369 to +385
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new groupMap* methods add non-trivial behavior but there are currently no unit/property tests covering them for NonEmptySeq (unlike NonEmptyList, which has explicit groupMap/groupMapReduce* tests). Please add tests in NonEmptySeqSuite covering groupMap, groupMapNem, groupMapReduce, groupMapReduceNem, groupMapReduceWith, and groupMapReduceWithNem (e.g., consistency with toSeq.toList.groupBy + map/reduce).

Copilot uses AI. Check for mistakes.

/**
* Groups elements inside this `NonEmptySeq` 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.{NonEmptyMap, NonEmptySeq}
* scala> import cats.syntax.all._
* scala> val neSeq = NonEmptySeq.of(12, -2, 3, -5)
* scala> val expectedResult = NonEmptyMap.of(false -> NonEmptySeq.of("-2", "-5"), true -> NonEmptySeq.of("12", "3"))
* scala> val result = neSeq.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, NonEmptySeq[B]] =
NonEmptyMap.fromMapUnsafe(groupMap(key)(f))

/**
* Groups elements inside this `NonEmptySeq` 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.NonEmptySeq
* scala> import cats.syntax.all._
* scala> val neSeq = NonEmptySeq.of("Hello", "World", "Goodbye", "World")
* scala> val expectedResult = SortedMap("goodbye" -> 1, "hello" -> 1, "world" -> 2)
* scala> val result = neSeq.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]): SortedMap[K, B] =
groupMapReduceWith(key)(f)(B.combine)

/**
* Groups elements inside this `NonEmptySeq` 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.{NonEmptyMap, NonEmptySeq}
* scala> import cats.syntax.all._
* scala> val neSeq = NonEmptySeq.of("Hello", "World", "Goodbye", "World")
* scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2)
* scala> val result = neSeq.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] =
NonEmptyMap.fromMapUnsafe(groupMapReduce(key)(f))

/**
* Groups elements inside this `NonEmptySeq` 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.NonEmptySeq
* scala> import cats.syntax.all._
* scala> val neSeq = NonEmptySeq.of("Hello", "World", "Goodbye", "World")
* scala> val expectedResult = SortedMap("goodbye" -> 1, "hello" -> 1, "world" -> 2)
* scala> val result = neSeq.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 = TreeMap.empty[K, B]

for { elem <- toSeq } {
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 `NonEmptySeq` 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.{NonEmptyMap, NonEmptySeq}
* scala> import cats.syntax.all._
* scala> val neSeq = NonEmptySeq.of("Hello", "World", "Goodbye", "World")
* scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2)
* scala> val result = neSeq.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] =
NonEmptyMap.fromMapUnsafe(groupMapReduceWith(key)(f)(combine))

/**
* Partitions elements in fixed size `NonEmptySeq`s.
*
Expand Down
157 changes: 157 additions & 0 deletions core/src/main/scala/cats/data/NonEmptyVector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,163 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A])
final def groupByNem[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NonEmptyVector[A]] =
NonEmptyMap.fromMapUnsafe(groupBy(f))

/**
* Groups elements inside this `NonEmptyVector` 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.NonEmptyVector
* scala> import cats.syntax.all._
* scala> val nev = NonEmptyVector.of(12, -2, 3, -5)
* scala> val expectedResult = SortedMap(false -> NonEmptyVector.of("-2", "-5"), true -> NonEmptyVector.of("12", "3"))
* scala> val result = nev.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, NonEmptyVector[B]] = {
implicit val ordering: Ordering[K] = K.toOrdering
var m = TreeMap.empty[K, mutable.Builder[B, Vector[B]]]

for { elem <- toVector } {
val k = key(elem)

m.get(k) match {
case None => m += ((k, Vector.newBuilder[B] += f(elem)))
case Some(builder) => builder += f(elem)
}
}

m.map { case (k, v) =>
(k, NonEmptyVector.fromVectorUnsafe(v.result()))
}: TreeMap[K, NonEmptyVector[B]]
}
Comment on lines +362 to +378
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new groupMap* methods add non-trivial behavior but there are currently no unit/property tests covering them for NonEmptyVector (unlike NonEmptyList, which has explicit groupMap/groupMapReduce* tests). Please add tests in NonEmptyVectorSuite covering groupMap, groupMapNem, groupMapReduce, groupMapReduceNem, groupMapReduceWith, and groupMapReduceWithNem (e.g., consistency with toVector.toList.groupBy + map/reduce).

Copilot uses AI. Check for mistakes.

/**
* Groups elements inside this `NonEmptyVector` 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.{NonEmptyMap, NonEmptyVector}
* scala> import cats.syntax.all._
* scala> val nev = NonEmptyVector.of(12, -2, 3, -5)
* scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyVector.of("-2", "-5"), true -> NonEmptyVector.of("12", "3"))
* scala> val result = nev.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, NonEmptyVector[B]] =
NonEmptyMap.fromMapUnsafe(groupMap(key)(f))

/**
* Groups elements inside this `NonEmptyVector` 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.NonEmptyVector
* scala> import cats.syntax.all._
* scala> val nev = NonEmptyVector.of("Hello", "World", "Goodbye", "World")
* scala> val expectedResult = SortedMap("goodbye" -> 1, "hello" -> 1, "world" -> 2)
* scala> val result = nev.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]): SortedMap[K, B] =
groupMapReduceWith(key)(f)(B.combine)

/**
* Groups elements inside this `NonEmptyVector` 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.{NonEmptyMap, NonEmptyVector}
* scala> import cats.syntax.all._
* scala> val nev = NonEmptyVector.of("Hello", "World", "Goodbye", "World")
* scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2)
* scala> val result = nev.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] =
NonEmptyMap.fromMapUnsafe(groupMapReduce(key)(f))

/**
* Groups elements inside this `NonEmptyVector` 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.NonEmptyVector
* scala> import cats.syntax.all._
* scala> val nev = NonEmptyVector.of("Hello", "World", "Goodbye", "World")
* scala> val expectedResult = SortedMap("goodbye" -> 1, "hello" -> 1, "world" -> 2)
* scala> val result = nev.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 = TreeMap.empty[K, B]

for { elem <- toVector } {
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 `NonEmptyVector` 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.{NonEmptyMap, NonEmptyVector}
* scala> import cats.syntax.all._
* scala> val nev = NonEmptyVector.of("Hello", "World", "Goodbye", "World")
* scala> val expectedResult = NonEmptyMap.of("goodbye" -> 1, "hello" -> 1, "world" -> 2)
* scala> val result = nev.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] =
NonEmptyMap.fromMapUnsafe(groupMapReduceWith(key)(f)(combine))

/**
* Partitions elements in fixed size `NonEmptyVector`s.
*
Expand Down
Loading