From de99d927a6e70e5a9b9a0ae441f4c25f035c38f8 Mon Sep 17 00:00:00 2001 From: Alexander Semenov Date: Thu, 28 Jul 2016 14:26:26 +0300 Subject: [PATCH 1/3] Add OneAnd.distinct method. --- core/src/main/scala/cats/data/OneAnd.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index ae42654e4e..58493e3f26 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -2,6 +2,7 @@ package cats package data import scala.annotation.tailrec +import scala.collection.GenSeqLike import scala.collection.mutable.Builder import cats.instances.stream._ @@ -92,6 +93,14 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { */ def show(implicit A: Show[A], FA: Show[F[A]]): String = s"OneAnd(${A.show(head)}, ${FA.show(tail)})" + + /** + * Remove duplicates. Duplicates are checked using universal equality provided by .equals. + */ + def distinct(implicit ev: F[A] <:< GenSeqLike[A, F[A]]): OneAnd[F, A] = { + val newTail = ev(tail).filter(_ != head).distinct + OneAnd(head, newTail) + } } private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { From 308dcc9ff8cfc63b7184b05fd7d5e52f1bdfca0f Mon Sep 17 00:00:00 2001 From: Alexander Semenov Date: Thu, 28 Jul 2016 22:49:12 +0300 Subject: [PATCH 2/3] Add distinct to NonEmptyList and NonEmptyVector --- core/src/main/scala/cats/data/NonEmptyList.scala | 15 +++++++++++++++ .../main/scala/cats/data/NonEmptyVector.scala | 16 +++++++++++++++- core/src/main/scala/cats/data/OneAnd.scala | 9 --------- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index cebb6a7c27..5f638f45cd 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -5,6 +5,7 @@ import cats.instances.list._ import cats.syntax.order._ import scala.annotation.tailrec +import scala.collection.immutable.TreeSet import scala.collection.mutable.ListBuffer /** @@ -105,6 +106,20 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { def show(implicit A: Show[A]): String = toList.iterator.map(A.show).mkString("NonEmptyList(", ", ", ")") + /** + * Remove duplicates. Duplicates are checked using `Order[_]` instance. + */ + def distinct(implicit O: Order[A]): NonEmptyList[A] = { + implicit val ord = O.toOrdering + + val buf = ListBuffer.empty[A] + tail.foldLeft(TreeSet(head)) { (elementsSoFar, a) => + if (elementsSoFar(a)) elementsSoFar else { buf += a; elementsSoFar + a } + } + + NonEmptyList(head, buf.toList) + } + override def toString: String = s"NonEmpty$toList" } diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index a9fe0077dc..3fd36d4f54 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -2,7 +2,7 @@ package cats package data import scala.annotation.tailrec -import scala.collection.immutable.VectorBuilder +import scala.collection.immutable.{TreeSet, VectorBuilder} import cats.instances.vector._ /** @@ -129,6 +129,20 @@ final case class NonEmptyVector[A] private (toVector: Vector[A]) { s"NonEmpty${Show[Vector[A]].show(toVector)}" def length: Int = toVector.length + + /** + * Remove duplicates. Duplicates are checked using `Order[_]` instance. + */ + def distinct(implicit O: Order[A]): NonEmptyVector[A] = { + implicit val ord = O.toOrdering + + val buf = Vector.newBuilder[A] + tail.foldLeft(TreeSet(head)) { (elementsSoFar, a) => + if (elementsSoFar(a)) elementsSoFar else { buf += a; elementsSoFar + a } + } + + NonEmptyVector(head, buf.result()) + } } private[data] sealed trait NonEmptyVectorInstances { diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 58493e3f26..ae42654e4e 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -2,7 +2,6 @@ package cats package data import scala.annotation.tailrec -import scala.collection.GenSeqLike import scala.collection.mutable.Builder import cats.instances.stream._ @@ -93,14 +92,6 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { */ def show(implicit A: Show[A], FA: Show[F[A]]): String = s"OneAnd(${A.show(head)}, ${FA.show(tail)})" - - /** - * Remove duplicates. Duplicates are checked using universal equality provided by .equals. - */ - def distinct(implicit ev: F[A] <:< GenSeqLike[A, F[A]]): OneAnd[F, A] = { - val newTail = ev(tail).filter(_ != head).distinct - OneAnd(head, newTail) - } } private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { From dc4e83b286172911bdafb8df51e5abcf3b64d575 Mon Sep 17 00:00:00 2001 From: Alexander Semenov Date: Fri, 29 Jul 2016 00:01:51 +0300 Subject: [PATCH 3/3] Add test cases for NonEmptyList#distinct and NonEmptyVector#distinct --- tests/src/test/scala/cats/tests/NonEmptyListTests.scala | 6 ++++++ tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index a12bd35ec1..2985b357e7 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -178,6 +178,12 @@ class NonEmptyListTests extends CatsSuite { (i :: nel).toList should === (i :: nel.toList) } } + + test("NonEmptyList#distinct is consistent with List#distinct") { + forAll { nel: NonEmptyList[Int] => + nel.distinct.toList should === (nel.toList.distinct) + } + } } 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 5fd419252f..51616f4546 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala @@ -218,6 +218,12 @@ class NonEmptyVectorTests extends CatsSuite { } } } + + test("NonEmptyVector#distinct is consistent with Vector#distinct") { + forAll { nonEmptyVector: NonEmptyVector[Int] => + nonEmptyVector.distinct.toVector should === (nonEmptyVector.toVector.distinct) + } + } } class ReducibleNonEmptyVectorCheck extends ReducibleCheck[NonEmptyVector]("NonEmptyVector") {