diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 4de780ecca..3c86a0046f 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -244,6 +244,29 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) { go(head, tail, Nil) } + /** + * Zips this `NonEmptyList` with another `NonEmptyList` and applies a function for each pair of elements. + * + * {{{ + * scala> import cats.data.NonEmptyList + * scala> val as = NonEmptyList.of(1, 2, 3) + * scala> val bs = NonEmptyList.of("A", "B", "C") + * scala> as.zipWith(bs)(_ + _) + * res0: cats.data.NonEmptyList[String] = NonEmptyList(1A, 2B, 3C) + * }}} + */ + def zipWith[B, C](b: NonEmptyList[B])(f: (A, B) => C): NonEmptyList[C] = { + + @tailrec + def zwRev(as: List[A], bs: List[B], acc: List[C]): List[C] = (as, bs) match { + case (Nil, _) => acc + case (_, Nil) => acc + case (x :: xs, y :: ys) => zwRev(xs, ys, f(x, y) :: acc) + } + + NonEmptyList(f(head, b.head), zwRev(tail, b.tail, Nil).reverse) + } + /** * Zips each element of this `NonEmptyList` with its index. * diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 5b32ca145a..49d04d9ef6 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -184,6 +184,20 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) extends AnyVal NonEmptyVector(head, buf.result()) } + + /** + * Zips this `NonEmptyVector` with another `NonEmptyVector` and applies a function for each pair of elements. + * + * {{{ + * scala> import cats.data.NonEmptyVector + * scala> val as = NonEmptyVector.of(1, 2, 3) + * scala> val bs = NonEmptyVector.of("A", "B", "C") + * scala> as.zipWith(bs)(_ + _) + * res0: cats.data.NonEmptyVector[String] = NonEmptyVector(1A, 2B, 3C) + * }}} + */ + def zipWith[B, C](b: NonEmptyVector[B])(f: (A, B) => C): NonEmptyVector[C] = + NonEmptyVector.fromVectorUnsafe((toVector, b.toVector).zipped.map(f)) } private[data] sealed trait NonEmptyVectorInstances { diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index af5a8fac05..4c6a4b5b17 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -280,6 +280,12 @@ class NonEmptyListTests extends CatsSuite { NonEmptyList.fromReducible(xs) should === (Reducible[NonEmptyVector].toNonEmptyList(xs)) } } + + test("NonEmptyList#zipWith is consistent with List#zip and then List#map") { + forAll { (a: NonEmptyList[Int], b: NonEmptyList[Int], f: (Int, Int) => Int) => + a.zipWith(b)(f).toList should === (a.toList.zip(b.toList).map {case (x, y) => f(x, y)}) + } + } } class ReducibleNonEmptyListCheck extends ReducibleCheck[NonEmptyList]("NonEmptyList") { diff --git a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala index 81f7dda833..a183825ea8 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala @@ -313,6 +313,12 @@ class NonEmptyVectorTests extends CatsSuite { nonEmptyVector.distinct.toVector should === (nonEmptyVector.toVector.distinct) } } + + test("NonEmptyVector#zipWith is consistent with Vector#zip and then Vector#map") { + forAll { (a: NonEmptyVector[Int], b: NonEmptyVector[Int], f: (Int, Int) => Int) => + a.zipWith(b)(f).toVector should === (a.toVector.zip(b.toVector).map { case (x, y) => f(x, y)}) + } + } } class ReducibleNonEmptyVectorCheck extends ReducibleCheck[NonEmptyVector]("NonEmptyVector") {