diff --git a/core/src/main/scala-2.13+/cats/data/NonEmptyLazyList.scala b/core/src/main/scala-2.13+/cats/data/NonEmptyLazyList.scala index baad1d3521..eb49b39c19 100644 --- a/core/src/main/scala-2.13+/cats/data/NonEmptyLazyList.scala +++ b/core/src/main/scala-2.13+/cats/data/NonEmptyLazyList.scala @@ -5,7 +5,8 @@ import NonEmptyLazyList.create import kernel.PartialOrder import instances.lazyList._ -import scala.collection.immutable.TreeSet +import scala.collection.immutable.{SortedMap, TreeMap, TreeSet} +import scala.collection.mutable object NonEmptyLazyList extends NonEmptyLazyListInstances { @@ -53,7 +54,9 @@ object NonEmptyLazyList extends NonEmptyLazyListInstances { new NonEmptyLazyListOps(value) } -class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A]) extends AnyVal { +class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A]) + extends AnyVal + with NonEmptyCollection[A, LazyList, NonEmptyLazyList] { /** * Converts this NonEmptyLazyList to a `LazyList` @@ -218,7 +221,14 @@ class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A]) extends Any * Finds the first element of this `NonEmptyLazyList` for which the given partial * function is defined, and applies the partial function to it. */ - final def collectLazyList[B](pf: PartialFunction[A, B]): Option[B] = toLazyList.collectFirst(pf) + @deprecated("Use collectFirst", "2.2.0-M1") + final def collectLazyList[B](pf: PartialFunction[A, B]): Option[B] = collectFirst(pf) + + /** + * Finds the first element of this `NonEmptyLazyList` for which the given partial + * function is defined, and applies the partial function to it. + */ + final def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = toLazyList.collectFirst(pf) /** * Filters all elements of this NonEmptyLazyList that do not satisfy the given predicate. @@ -326,6 +336,116 @@ class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A]) extends Any create(buf.result()) } + + /** + * Sorts this `NonEmptyLazyList` according to an `Order` on transformed `B` from `A` + * + * {{{ + * scala> import cats.data.NonEmptyLazyList + * scala> import cats.instances.int._ + * scala> val nel = NonEmptyLazyList(('a', 4), ('z', 1), ('e', 22)) + * scala> nel.sortBy(_._2).toLazyList.toList + * res0: List[(Char, Int)] = List((z,1), (a,4), (e,22)) + * }}} + */ + final def sortBy[B](f: A => B)(implicit B: Order[B]): NonEmptyLazyList[A] = + // safe: sorting a NonEmptyList cannot produce an empty List + create(toLazyList.sortBy(f)(B.toOrdering)) + + /** + * Sorts this `NonEmptyList` according to an `Order` + * + * {{{ + * scala> import cats.data.NonEmptyLazyList + * scala> import cats.instances.int._ + * scala> val nel = NonEmptyLazyList(12, 4, 3, 9) + * scala> nel.sorted.toLazyList.toList + * res0: List[Int] = List(3, 4, 9, 12) + * }}} + */ + final def sorted[AA >: A](implicit AA: Order[AA]): NonEmptyLazyList[AA] = + create(toLazyList.sorted(AA.toOrdering)) + + /** + * Groups elements inside this `NonEmptyLazyList` according to the `Order` + * of the keys produced by the given mapping function. + * + * {{{ + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.data.NonEmptyLazyList + * scala> import cats.implicits._ + * scala> val nel = NonEmptyLazyList(12, -2, 3, -5) + * scala> val expectedResult = SortedMap(false -> NonEmptyLazyList(-2, -5), true -> NonEmptyLazyList(12, 3)) + * scala> val result = nel.groupBy(_ >= 0) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupBy[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyLazyList[A]] = { + implicit val ordering: Ordering[B] = B.toOrdering + var m = TreeMap.empty[B, mutable.Builder[A, LazyList[A]]] + + for { elem <- toLazyList } { + val k = f(elem) + + m.get(k) match { + case None => m += ((k, LazyList.newBuilder[A] += elem)) + case Some(builder) => builder += elem + } + } + + m.map { + case (k, v) => (k, create(v.result)) + }: TreeMap[B, NonEmptyLazyList[A]] + } + + /** + * Groups elements inside this `NonEmptyLazyList` according to the `Order` + * of the keys produced by the given mapping function. + * + * {{{ + * scala> import cats.data.{NonEmptyLazyList, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nel = NonEmptyLazyList(12, -2, 3, -5) + * scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyLazyList(-2, -5), true -> NonEmptyLazyList(12, 3)) + * scala> val result = nel.groupByNem(_ >= 0) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupByNem[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NonEmptyLazyList[A]] = + NonEmptyMap.fromMapUnsafe(groupBy(f)) + + /** + * Creates new `NonEmptyMap`, similarly to List#toMap from scala standard library. + *{{{ + * scala> import cats.data.{NonEmptyLazyList, NonEmptyMap} + * scala> import cats.implicits._ + * scala> val nel = NonEmptyLazyList.fromLazyListPrepend((0, "a"), LazyList((1, "b"),(0, "c"), (2, "d"))) + * scala> val expectedResult = NonEmptyMap.of(0 -> "c", 1 -> "b", 2 -> "d") + * scala> val result = nel.toNem + * scala> result === expectedResult + * res0: Boolean = true + *}}} + * + */ + final def toNem[T, U](implicit ev: A <:< (T, U), order: Order[T]): NonEmptyMap[T, U] = + NonEmptyMap.fromMapUnsafe(SortedMap(toLazyList.map(ev): _*)(order.toOrdering)) + + /** + * Creates new `NonEmptySet`, similarly to List#toSet from scala standard library. + *{{{ + * scala> import cats.data._ + * scala> import cats.instances.int._ + * scala> val nel = NonEmptyLazyList.fromLazyListPrepend(1, LazyList(2,2,3,4)) + * scala> nel.toNes + * res0: cats.data.NonEmptySet[Int] = TreeSet(1, 2, 3, 4) + *}}} + */ + final def toNes[B >: A](implicit order: Order[B]): NonEmptySet[B] = + NonEmptySet.of(head, tail: _*) + + final def show[AA >: A](implicit AA: Show[AA]): String = s"NonEmpty${Show[LazyList[AA]].show(toLazyList)}" } sealed abstract private[data] class NonEmptyLazyListInstances extends NonEmptyLazyListInstances1 { @@ -375,7 +495,7 @@ sealed abstract private[data] class NonEmptyLazyListInstances extends NonEmptyLa Semigroup[LazyList[A]].asInstanceOf[Semigroup[NonEmptyLazyList[A]]] implicit def catsDataShowForNonEmptyLazyList[A](implicit A: Show[A]): Show[NonEmptyLazyList[A]] = - Show.show[NonEmptyLazyList[A]](nell => s"NonEmpty${Show[LazyList[A]].show(nell.toLazyList)}") + Show.show[NonEmptyLazyList[A]](_.show) implicit def catsDataParallelForNonEmptyLazyList: Parallel.Aux[NonEmptyLazyList, OneAnd[ZipLazyList, *]] = new Parallel[NonEmptyLazyList] { diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index b76ba3116f..d647c9ee4e 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -5,8 +5,7 @@ import Chain._ import cats.kernel.instances.StaticMethods import scala.annotation.tailrec -import scala.collection.immutable.SortedMap -import scala.collection.immutable.TreeSet +import scala.collection.immutable.{SortedMap, TreeSet} import scala.collection.mutable.ListBuffer /** @@ -324,6 +323,18 @@ sealed abstract class Chain[+A] { result } + /** + * Zips each element of this `Chain` with its index. + */ + final def zipWithIndex: Chain[(A, Int)] = this match { + case Empty => Empty + case Singleton(a) => Singleton((a, 0)) + case Append(left, right) => + val leftSize = left.length.toInt + Append(left.zipWithIndex, right.zipWithIndex.map { case (a, i) => (a, leftSize + i) }) + case Wrap(seq) => Wrap(seq.zipWithIndex) + } + /** * Groups elements inside this `Chain` according to the `Order` * of the keys produced by the given mapping function. @@ -529,6 +540,20 @@ sealed abstract class Chain[+A] { } result } + + final def sortBy[B](f: A => B)(implicit B: Order[B]): Chain[A] = this match { + case Empty => this + case Singleton(_) => this + case Append(_, _) => Wrap(toVector.sortBy(f)(B.toOrdering)) + case Wrap(seq) => Wrap(seq.sortBy(f)(B.toOrdering)) + } + + final def sorted[AA >: A](implicit AA: Order[AA]): Chain[AA] = this match { + case Empty => this + case Singleton(_) => this + case Append(_, _) => Wrap(toVector.sorted(AA.toOrdering)) + case Wrap(seq) => Wrap(seq.sorted(AA.toOrdering)) + } } object Chain extends ChainInstances { diff --git a/core/src/main/scala/cats/data/NonEmptyChain.scala b/core/src/main/scala/cats/data/NonEmptyChain.scala index 22f3b87f84..c78c9310ea 100644 --- a/core/src/main/scala/cats/data/NonEmptyChain.scala +++ b/core/src/main/scala/cats/data/NonEmptyChain.scala @@ -4,6 +4,7 @@ package data import NonEmptyChainImpl.create import cats.{Order, Semigroup} import cats.kernel._ +import scala.collection.immutable.SortedMap private[data] object NonEmptyChainImpl extends NonEmptyChainInstances with ScalaVersionSpecificNonEmptyChainImpl { // The following 3 types are components of a technique to @@ -52,7 +53,9 @@ private[data] object NonEmptyChainImpl extends NonEmptyChainInstances with Scala new NonEmptyChainOps(value) } -class NonEmptyChainOps[A](private val value: NonEmptyChain[A]) extends AnyVal { +class NonEmptyChainOps[A](private val value: NonEmptyChain[A]) + extends AnyVal + with NonEmptyCollection[A, Chain, NonEmptyChain] { /** * Converts this chain to a `Chain` @@ -382,6 +385,8 @@ class NonEmptyChainOps[A](private val value: NonEmptyChain[A]) extends AnyVal { 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) + final def iterator: Iterator[A] = toChain.iterator final def reverseIterator: Iterator[A] = toChain.reverseIterator @@ -396,6 +401,14 @@ class NonEmptyChainOps[A](private val value: NonEmptyChain[A]) extends AnyVal { final def distinct[AA >: A](implicit O: Order[AA]): NonEmptyChain[AA] = create(toChain.distinct[AA]) + final def sortBy[B](f: A => B)(implicit B: Order[B]): NonEmptyChain[A] = create(toChain.sortBy(f)) + final def sorted[AA >: A](implicit AA: Order[AA]): NonEmptyChain[AA] = create(toChain.sorted[AA]) + final def toNem[T, V](implicit ev: A <:< (T, V), order: Order[T]): NonEmptyMap[T, V] = + NonEmptyMap.fromMapUnsafe(SortedMap(toChain.toVector.map(ev): _*)(order.toOrdering)) + final def toNes[B >: A](implicit order: Order[B]): NonEmptySet[B] = NonEmptySet.of(head, tail.toVector: _*) + final def zipWithIndex: NonEmptyChain[(A, Int)] = create(toChain.zipWithIndex) + + final def show[AA >: A](implicit AA: Show[AA]): String = s"NonEmpty${Show[Chain[AA]].show(toChain)}" } sealed abstract private[data] class NonEmptyChainInstances extends NonEmptyChainInstances1 { @@ -454,7 +467,7 @@ sealed abstract private[data] class NonEmptyChainInstances extends NonEmptyChain Semigroup[Chain[A]].asInstanceOf[Semigroup[NonEmptyChain[A]]] implicit def catsDataShowForNonEmptyChain[A](implicit A: Show[A]): Show[NonEmptyChain[A]] = - Show.show[NonEmptyChain[A]](nec => s"NonEmpty${Show[Chain[A]].show(nec.toChain)}") + Show.show[NonEmptyChain[A]](_.show) } diff --git a/core/src/main/scala/cats/data/NonEmptyCollection.scala b/core/src/main/scala/cats/data/NonEmptyCollection.scala new file mode 100644 index 0000000000..eebef180a0 --- /dev/null +++ b/core/src/main/scala/cats/data/NonEmptyCollection.scala @@ -0,0 +1,40 @@ +package cats.data + +import cats.Show +import cats.kernel.{Order, Semigroup} + +private[cats] trait NonEmptyCollection[+A, U[+_], NE[+_]] extends Any { + def head: A + def tail: U[A] + def last: A + def init: U[A] + + def iterator: Iterator[A] + + def map[B](f: A => B): NE[B] + def reverse: NE[A] + def prepend[AA >: A](a: AA): NE[AA] + def append[AA >: A](a: AA): NE[AA] + + def filter(f: A => Boolean): U[A] + def filterNot(p: A => Boolean): U[A] + def collect[B](pf: PartialFunction[A, B]): U[B] + def find(p: A => Boolean): Option[A] + def exists(p: A => Boolean): Boolean + def forall(p: A => Boolean): Boolean + + def foldLeft[B](b: B)(f: (B, A) => B): B + def reduce[AA >: A](implicit S: Semigroup[AA]): AA + + def zipWith[B, C](b: NE[B])(f: (A, B) => C): NE[C] + def zipWithIndex: NE[(A, Int)] + + def distinct[AA >: A](implicit O: Order[AA]): NE[AA] + def sortBy[B](f: A => B)(implicit B: Order[B]): NE[A] + def sorted[AA >: A](implicit AA: Order[AA]): NE[AA] + def groupByNem[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NE[A]] + def toNem[T, V](implicit ev: A <:< (T, V), order: Order[T]): NonEmptyMap[T, V] + def toNes[B >: A](implicit order: Order[B]): NonEmptySet[B] + + def show[AA >: A](implicit AA: Show[AA]): String +} diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 2437a4bd07..99d7566d9c 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -13,7 +13,7 @@ import scala.collection.mutable.ListBuffer * A data type which represents a non empty list of A, with * single element (head) and optional structure (tail). */ -final case class NonEmptyList[+A](head: A, tail: List[A]) { +final case class NonEmptyList[+A](head: A, tail: List[A]) extends NonEmptyCollection[A, List, NonEmptyList] { /** * Return the head and tail into a single list @@ -55,6 +55,8 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) { case t => head :: t.init } + final def iterator: Iterator[A] = toList.iterator + /** * The size of this NonEmptyList * diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 8f14b22217..af021ebfce 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -5,7 +5,8 @@ import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.instances.vector._ import scala.annotation.tailrec -import scala.collection.immutable.{TreeSet, VectorBuilder} +import scala.collection.mutable +import scala.collection.immutable.{SortedMap, TreeMap, TreeSet, VectorBuilder} import kernel.compat.scalaVersionSpecific._ /** @@ -15,7 +16,9 @@ import kernel.compat.scalaVersionSpecific._ * `NonEmptyVector`. However, due to https://issues.scala-lang.org/browse/SI-6601, on * Scala 2.10, this may be bypassed due to a compiler bug. */ -final class NonEmptyVector[+A] private (val toVector: Vector[A]) extends AnyVal { +final class NonEmptyVector[+A] private (val toVector: Vector[A]) + extends AnyVal + with NonEmptyCollection[A, Vector, NonEmptyVector] { /** Gets the element at the index, if it exists */ def get(i: Int): Option[A] = @@ -42,6 +45,8 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) extends AnyVal def init: Vector[A] = toVector.init + final def iterator: Iterator[A] = toVector.iterator + /** * Remove elements not matching the predicate * @@ -233,6 +238,85 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) extends AnyVal def sorted[AA >: A](implicit AA: Order[AA]): NonEmptyVector[AA] = new NonEmptyVector(toVector.sorted(AA.toOrdering)) + + /** + * Groups elements inside this `NonEmptyVector` according to the `Order` + * of the keys produced by the given mapping function. + * + * {{{ + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.data.NonEmptyVector + * scala> import cats.implicits._ + * 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.groupBy(_ >= 0) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupBy[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyVector[A]] = { + implicit val ordering: Ordering[B] = B.toOrdering + var m = TreeMap.empty[B, mutable.Builder[A, Vector[A]]] + + for { elem <- toVector } { + val k = f(elem) + + m.get(k) match { + case None => m += ((k, Vector.newBuilder[A] += elem)) + case Some(builder) => builder += elem + } + } + + m.map { + case (k, v) => (k, NonEmptyVector.fromVectorUnsafe(v.result)) + }: TreeMap[B, NonEmptyVector[A]] + } + + /** + * Groups elements inside this `NonEmptyVector` according to the `Order` + * of the keys produced by the given mapping function. + * + * {{{ + * scala> import cats.data.{NonEmptyMap, NonEmptyVector} + * scala> import cats.implicits._ + * scala> val nel = NonEmptyVector.of(12, -2, 3, -5) + * scala> val expectedResult = NonEmptyMap.of(false -> NonEmptyVector.of(-2, -5), true -> NonEmptyVector.of(12, 3)) + * scala> val result = nel.groupByNem(_ >= 0) + * scala> result === expectedResult + * res0: Boolean = true + * }}} + */ + final def groupByNem[B](f: A => B)(implicit B: Order[B]): NonEmptyMap[B, NonEmptyVector[A]] = + NonEmptyMap.fromMapUnsafe(groupBy(f)) + + /** + * Creates new `NonEmptyMap`, similarly to List#toMap from scala standard library. + *{{{ + * scala> import cats.data.{NonEmptyMap, NonEmptyVector} + * scala> import cats.implicits._ + * scala> val nev = NonEmptyVector((0, "a"), Vector((1, "b"),(0, "c"), (2, "d"))) + * scala> val expectedResult = NonEmptyMap.of(0 -> "c", 1 -> "b", 2 -> "d") + * scala> val result = nev.toNem + * scala> result === expectedResult + * res0: Boolean = true + *}}} + * + */ + final def toNem[T, U](implicit ev: A <:< (T, U), order: Order[T]): NonEmptyMap[T, U] = + NonEmptyMap.fromMapUnsafe(SortedMap(toVector.map(ev): _*)(order.toOrdering)) + + /** + * Creates new `NonEmptySet`, similarly to List#toSet from scala standard library. + *{{{ + * scala> import cats.data._ + * scala> import cats.instances.int._ + * scala> val nev = NonEmptyVector(1, Vector(2,2,3,4)) + * scala> nev.toNes + * res0: cats.data.NonEmptySet[Int] = TreeSet(1, 2, 3, 4) + *}}} + */ + final def toNes[B >: A](implicit order: Order[B]): NonEmptySet[B] = + NonEmptySet.of(head, tail: _*) } @suppressUnusedImportWarningForScalaVersionSpecific diff --git a/tests/src/test/scala-2.13+/cats/tests/NonEmptyLazyListSuite.scala b/tests/src/test/scala-2.13+/cats/tests/NonEmptyLazyListSuite.scala index 17e49d10db..68666023ed 100644 --- a/tests/src/test/scala-2.13+/cats/tests/NonEmptyLazyListSuite.scala +++ b/tests/src/test/scala-2.13+/cats/tests/NonEmptyLazyListSuite.scala @@ -1,12 +1,14 @@ package cats package tests -import cats.data.NonEmptyLazyList +import cats.data.{NonEmptyLazyList, NonEmptyLazyListOps} import cats.kernel.laws.discipline.{EqTests, HashTests, OrderTests, PartialOrderTests, SemigroupTests} import cats.laws.discipline.{AlignTests, BimonadTests, NonEmptyTraverseTests, SemigroupKTests, SerializableTests} import cats.laws.discipline.arbitrary._ -class NonEmptyLazyListSuite extends CatsSuite { +class NonEmptyLazyListSuite extends NonEmptyCollectionSuite[LazyList, NonEmptyLazyList, NonEmptyLazyListOps] { + def toList[A](value: NonEmptyLazyList[A]): List[A] = value.toList + def underlyingToList[A](underlying: LazyList[A]): List[A] = underlying.toList checkAll("NonEmptyLazyList[Int]", SemigroupTests[NonEmptyLazyList[Int]].semigroup) checkAll(s"Semigroup[NonEmptyLazyList]", SerializableTests.serializable(Semigroup[NonEmptyLazyList[Int]])) @@ -101,6 +103,12 @@ class NonEmptyLazyListSuite extends CatsSuite { Either.catchNonFatal(NonEmptyLazyList.fromLazyListUnsafe(LazyList.empty[Int])).isLeft should ===(true) } + test("fromLazyListAppend is consistent with LazyList#:+") { + forAll { (lli: LazyList[Int], i: Int) => + NonEmptyLazyList.fromLazyListAppend(lli, i).toLazyList should ===(lli :+ i) + } + } + test("fromSeq . toList . iterator is id") { forAll { (ci: NonEmptyLazyList[Int]) => NonEmptyLazyList.fromSeq(ci.iterator.toList) should ===(Option(ci)) diff --git a/tests/src/test/scala/cats/tests/ChainSuite.scala b/tests/src/test/scala/cats/tests/ChainSuite.scala index 06efaad3d5..f5bd01991c 100644 --- a/tests/src/test/scala/cats/tests/ChainSuite.scala +++ b/tests/src/test/scala/cats/tests/ChainSuite.scala @@ -161,6 +161,24 @@ class ChainSuite extends CatsSuite { } } + test("zipWithIndex is consistent with toList.zipWithIndex") { + forAll { (ci: Chain[Int]) => + ci.zipWithIndex.toList should ===(ci.toList.zipWithIndex) + } + } + + test("sortBy is consistent with toList.sortBy") { + forAll { (ci: Chain[Int], f: Int => String) => + ci.sortBy(f).toList should ===(ci.toList.sortBy(f)) + } + } + + test("sorted is consistent with toList.sorted") { + forAll { (ci: Chain[Int]) => + ci.sorted.toList should ===(ci.toList.sorted) + } + } + test("reverse . reverse is id") { forAll { (ci: Chain[Int]) => ci.reverse.reverse should ===(ci) diff --git a/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala b/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala index 21596ecff4..b4910c49a3 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala @@ -1,12 +1,15 @@ package cats package tests -import cats.data.{Chain, NonEmptyChain} +import cats.data.{Chain, NonEmptyChain, NonEmptyChainOps} import cats.kernel.laws.discipline.{EqTests, OrderTests, PartialOrderTests, SemigroupTests} import cats.laws.discipline.{AlignTests, BimonadTests, NonEmptyTraverseTests, SemigroupKTests, SerializableTests} import cats.laws.discipline.arbitrary._ -class NonEmptyChainSuite extends CatsSuite { +class NonEmptyChainSuite extends NonEmptyCollectionSuite[Chain, NonEmptyChain, NonEmptyChainOps] { + def toList[A](value: NonEmptyChain[A]): List[A] = value.toChain.toList + def underlyingToList[A](underlying: Chain[A]): List[A] = underlying.toList + checkAll("NonEmptyChain[Int]", SemigroupKTests[NonEmptyChain].semigroupK[Int]) checkAll("SemigroupK[NonEmptyChain]", SerializableTests.serializable(SemigroupK[NonEmptyChain])) diff --git a/tests/src/test/scala/cats/tests/NonEmptyCollectionSuite.scala b/tests/src/test/scala/cats/tests/NonEmptyCollectionSuite.scala new file mode 100644 index 0000000000..5d8feecb99 --- /dev/null +++ b/tests/src/test/scala/cats/tests/NonEmptyCollectionSuite.scala @@ -0,0 +1,158 @@ +package cats.tests + +import cats.data.NonEmptyCollection +import org.scalacheck.Arbitrary + +abstract class NonEmptyCollectionSuite[U[+_], NE[+_], NEC[x] <: NonEmptyCollection[x, U, NE]]( + implicit arbitraryU: Arbitrary[U[Int]], + arbitraryNE: Arbitrary[NE[Int]], + ev: NE[Int] => NEC[Int], + evPair: NE[(Int, Int)] => NEC[(Int, Int)] +) extends CatsSuite { + def toList[A](value: NE[A]): List[A] + def underlyingToList[A](underlying: U[A]): List[A] + + test("head is consistent with iterator.toList.head") { + forAll { (is: NE[Int]) => + is.head should ===(is.iterator.toList.head) + } + } + + test("tail is consistent with iterator.toList.tail") { + forAll { (is: NE[Int]) => + underlyingToList(is.tail) should ===(is.iterator.toList.tail) + } + } + + test("last is consistent with iterator.toList.last") { + forAll { (is: NE[Int]) => + is.last should ===(is.iterator.toList.last) + } + } + + test("init is consistent with iterator.toList.init") { + forAll { (is: NE[Int]) => + underlyingToList(is.init) should ===(is.iterator.toList.init) + } + } + + test("map is consistent with iterator.toList.map") { + forAll { (is: NE[Int], f: Int => String) => + toList(is.map(f)) should ===(is.iterator.toList.map(f)) + } + } + + test("reverse is consistent with iterator.toList.reverse") { + forAll { (is: NE[Int]) => + toList(is.reverse) should ===(is.iterator.toList.reverse) + } + } + + test("prepend is consistent with iterator.toList.::") { + forAll { (is: NE[Int], i: Int) => + toList(is.prepend(i)) should ===(i :: is.iterator.toList) + } + } + + test("append is consistent with iterator.toList.::") { + forAll { (is: NE[Int], i: Int) => + toList(is.append(i)) should ===(is.iterator.toList :+ i) + } + } + + test("filter is consistent with iterator.toList.filter") { + forAll { (is: NE[Int], pred: Int => Boolean) => + underlyingToList(is.filter(pred)) should ===(is.iterator.toList.filter(pred)) + } + } + + test("filterNot is consistent with iterator.toList.filterNot") { + forAll { (is: NE[Int], pred: Int => Boolean) => + underlyingToList(is.filterNot(pred)) should ===(is.iterator.toList.filterNot(pred)) + } + } + + test("collect is consistent with iterator.toList.collect") { + forAll { (is: NE[Int], pf: PartialFunction[Int, String]) => + underlyingToList(is.collect(pf)) should ===(is.iterator.toList.collect(pf)) + } + } + + test("find is consistent with iterator.toList.find") { + forAll { (is: NE[Int], pred: Int => Boolean) => + is.find(pred) should ===(is.iterator.toList.find(pred)) + } + } + + test("exists is consistent with iterator.toList.exists") { + forAll { (is: NE[Int], pred: Int => Boolean) => + is.exists(pred) should ===(is.iterator.toList.exists(pred)) + } + } + + test("forall is consistent with iterator.toList.forall") { + forAll { (is: NE[Int], pred: Int => Boolean) => + is.forall(pred) should ===(is.iterator.toList.forall(pred)) + } + } + + test("foldLeft is consistent with iterator.toList.foldLeft") { + forAll { (is: NE[Int], b: String, f: (String, Int) => String) => + is.foldLeft(b)(f) should ===(is.iterator.toList.foldLeft(b)(f)) + } + } + + test("reduce is consistent with iterator.toList.sum") { + forAll { (is: NE[Int]) => + is.reduce should ===(is.iterator.toList.sum) + } + } + + test("zipWith is consistent with iterator.toList.zip") { + forAll { (is: NE[Int], other: NE[Int], f: (Int, Int) => String) => + toList(is.zipWith(other)(f)) should ===(is.iterator.toList.zip(other.iterator.toList).map(Function.tupled(f))) + } + } + + test("zipWithIndex is consistent with iterator.toList.zipWithIndex") { + forAll { (is: NE[Int]) => + toList(is.zipWithIndex) should ===(is.iterator.toList.zipWithIndex) + } + } + + test("distinct is consistent with iterator.toList.distinct") { + forAll { (is: NE[Int]) => + toList(is.distinct) should ===(is.iterator.toList.distinct) + } + } + + test("sortBy is consistent with iterator.toList.sortBy") { + forAll { (is: NE[Int], f: Int => String) => + toList(is.sortBy(f)) should ===(is.iterator.toList.sortBy(f)) + } + } + + test("sorted is consistent with iterator.toList.sorted") { + forAll { (is: NE[Int]) => + toList(is.sorted) should ===(is.iterator.toList.sorted) + } + } + + test("groupByNem is consistent with iterator.toList.groupBy") { + forAll { (is: NE[Int], f: Int => String) => + is.groupByNem(f).toSortedMap.map { case (k, v) => (k, toList(v)) } should ===(is.iterator.toList.groupBy(f)) + } + } + + test("toNem is consistent with iterator.toMap") { + forAll { (is: NE[Int]) => + is.zipWithIndex.toNem.toSortedMap should ===(is.zipWithIndex.iterator.toMap) + } + } + + test("toNes is consistent with iterator.toSet") { + forAll { (is: NE[Int]) => + is.toNes.toSortedSet should ===(is.iterator.toSet) + } + } +} diff --git a/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala b/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala index db0b6d58ae..8e5926129f 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala @@ -18,7 +18,10 @@ import cats.laws.discipline.{ import scala.collection.immutable.SortedMap import scala.collection.immutable.SortedSet -class NonEmptyListSuite extends CatsSuite { +class NonEmptyListSuite extends NonEmptyCollectionSuite[List, NonEmptyList, NonEmptyList] { + def toList[A](value: NonEmptyList[A]): List[A] = value.toList + def underlyingToList[A](underlying: List[A]): List[A] = underlying + // Lots of collections here.. telling ScalaCheck to calm down a bit implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration(minSuccessful = 20, sizeRange = 5) diff --git a/tests/src/test/scala/cats/tests/NonEmptyVectorSuite.scala b/tests/src/test/scala/cats/tests/NonEmptyVectorSuite.scala index 706b6f557c..604d2e9ea1 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorSuite.scala @@ -21,7 +21,10 @@ import cats.platform.Platform import scala.util.Properties -class NonEmptyVectorSuite extends CatsSuite { +class NonEmptyVectorSuite extends NonEmptyCollectionSuite[Vector, NonEmptyVector, NonEmptyVector] { + def toList[A](value: NonEmptyVector[A]): List[A] = value.toList + def underlyingToList[A](underlying: Vector[A]): List[A] = underlying.toList + // Lots of collections here.. telling ScalaCheck to calm down a bit implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration(minSuccessful = 20, sizeRange = 5)