diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 220562b7d5..3131e12066 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -5,6 +5,7 @@ import Chain._ import scala.annotation.tailrec import scala.collection.immutable.SortedMap +import scala.collection.immutable.TreeSet import scala.collection.mutable.ListBuffer /** @@ -358,6 +359,24 @@ sealed abstract class Chain[+A] { iterX.hasNext == iterY.hasNext } + /** + * Remove duplicates. Duplicates are checked using `Order[_]` instance. + */ + def distinct[AA >: A](implicit O: Order[AA]): Chain[AA] = { + implicit val ord = O.toOrdering + + var alreadyIn = TreeSet.empty[AA] + + foldLeft(Chain.empty[AA]) { (elementsSoFar, b) => + if (alreadyIn.contains(b)) { + elementsSoFar + } else { + alreadyIn += b + elementsSoFar :+ b + } + } + } + def show[AA >: A](implicit AA: Show[AA]): String = { val builder = new StringBuilder("Chain(") var first = true diff --git a/core/src/main/scala/cats/data/NonEmptyChain.scala b/core/src/main/scala/cats/data/NonEmptyChain.scala index aa353447e8..ba55ce7907 100644 --- a/core/src/main/scala/cats/data/NonEmptyChain.scala +++ b/core/src/main/scala/cats/data/NonEmptyChain.scala @@ -372,6 +372,24 @@ class NonEmptyChainOps[A](val value: NonEmptyChain[A]) extends AnyVal { /** Reverses this `NonEmptyChain` */ final def reverse: NonEmptyChain[A] = create(toChain.reverse) + + /** + * Remove duplicates. Duplicates are checked using `Order[_]` instance. + */ + final def distinct[AA >: A](implicit O: Order[AA]): NonEmptyChain[AA] = { + implicit val ord = O.toOrdering + + var alreadyIn = TreeSet(head: AA) + + foldLeft(NonEmptyChain(head: AA)) { (elementsSoFar, b) => + if (alreadyIn.contains(b)) { + elementsSoFar + } else { + alreadyIn += b + elementsSoFar :+ b + } + } + } } sealed abstract private[data] class NonEmptyChainInstances extends NonEmptyChainInstances1 { diff --git a/tests/src/test/scala/cats/tests/ChainSuite.scala b/tests/src/test/scala/cats/tests/ChainSuite.scala index 2f2df8f89f..ae58c3618b 100644 --- a/tests/src/test/scala/cats/tests/ChainSuite.scala +++ b/tests/src/test/scala/cats/tests/ChainSuite.scala @@ -174,4 +174,10 @@ class ChainSuite extends CatsSuite { } } } + + test("Chain#distinct is consistent with List#distinct") { + forAll { a: Chain[Int] => + a.distinct.toList should ===(a.toList.distinct) + } + } } diff --git a/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala b/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala index 640db1db2a..b9939ee25c 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyChainSuite.scala @@ -133,4 +133,10 @@ class NonEmptyChainSuite extends CatsSuite { ci.reverse.toChain should ===(ci.toChain.reverse) } } + + test("NonEmptyChain#distinct is consistent with List#distinct") { + forAll { ci: NonEmptyChain[Int] => + ci.distinct.toList should ===(ci.toList.distinct) + } + } }