diff --git a/core/src/main/scala/cats/instances/all.scala b/core/src/main/scala/cats/instances/all.scala index a1204d4cc2d..f6d6fd4e6be 100644 --- a/core/src/main/scala/cats/instances/all.scala +++ b/core/src/main/scala/cats/instances/all.scala @@ -23,6 +23,7 @@ trait AllInstances with QueueInstances with SemigroupInstances with SetInstances + with SortedMapInstances with StreamInstances with StringInstances with SymbolInstances diff --git a/core/src/main/scala/cats/instances/package.scala b/core/src/main/scala/cats/instances/package.scala index 85dadb037cd..31c6f594254 100644 --- a/core/src/main/scala/cats/instances/package.scala +++ b/core/src/main/scala/cats/instances/package.scala @@ -20,6 +20,7 @@ package object instances { object list extends ListInstances object long extends LongInstances object map extends MapInstances + object sortedMap extends SortedMapInstances object monoid extends MonoidInstances object option extends OptionInstances object order extends OrderInstances diff --git a/core/src/main/scala/cats/instances/sortedMap.scala b/core/src/main/scala/cats/instances/sortedMap.scala new file mode 100644 index 00000000000..84a8d98f3ba --- /dev/null +++ b/core/src/main/scala/cats/instances/sortedMap.scala @@ -0,0 +1,173 @@ +package cats.instances + +import cats.{Always, Applicative, Eval, FlatMap, Foldable, Monoid, Show, Traverse} +import cats.kernel._ +import cats.kernel.instances.StaticMethods + +import scala.annotation.tailrec +import scala.collection.immutable.SortedMap +import scala.collection.mutable + +trait SortedMapInstances extends SortedMapInstances1 { + + implicit def catsKernelStdHashForSortedMap[K: Hash: Order, V: Hash]: Hash[SortedMap[K, V]] = + new SortedMapHash[K, V] + + implicit def catsKernelStdMonoidForSortedMap[K: Order, V: Semigroup]: Monoid[SortedMap[K, V]] = + new SortedMapMonoid[K, V] + + implicit def catsStdShowForSortedMap[A: Order, B](implicit showA: Show[A], showB: Show[B]): Show[SortedMap[A, B]] = + new Show[SortedMap[A, B]] { + def show(m: SortedMap[A, B]): String = + m.iterator + .map { case (a, b) => showA.show(a) + " -> " + showB.show(b) } + .mkString("SortedMap(", ", ", ")") + } + + + // scalastyle:off method.length + implicit def catsStdInstancesForSortedMap[K: Order]: Traverse[SortedMap[K, ?]] with FlatMap[SortedMap[K, ?]] = + new Traverse[SortedMap[K, ?]] with FlatMap[SortedMap[K, ?]] { + + implicit val orderingK: Ordering[K] = Order[K].toOrdering + + def traverse[G[_], A, B](fa: SortedMap[K, A])(f: A => G[B])(implicit G: Applicative[G]): G[SortedMap[K, B]] = { + val gba: Eval[G[SortedMap[K, B]]] = Always(G.pure(SortedMap.empty(Order[K].toOrdering))) + Foldable.iterateRight(fa.iterator, gba){ (kv, lbuf) => + G.map2Eval(f(kv._2), lbuf)({ (b, buf) => buf + (kv._1 -> b)}) + }.value + } + + def flatMap[A, B](fa: SortedMap[K,A])(f: A => SortedMap[K,B]): SortedMap[K,B] = + fa.flatMap { case (k, a) => f(a).get(k).map((k, _)) } + + override def map[A, B](fa: SortedMap[K, A])(f: A => B): SortedMap[K, B] = + fa.mapValues(f) + + override def map2[A, B, Z](fa: SortedMap[K, A], fb: SortedMap[K, B])(f: (A, B) => Z): SortedMap[K, Z] = + if (fb.isEmpty) SortedMap.empty(Order[K].toOrdering) // do O(1) work if fb is empty + else ??? //fa.flatMap { case (k, a) => fb.get(k).map(b => (k, f(a, b))) } + + override def map2Eval[A, B, Z](fa: SortedMap[K, A], fb: Eval[SortedMap[K, B]])(f: (A, B) => Z): Eval[SortedMap[K, Z]] = + if (fa.isEmpty) Eval.now(SortedMap.empty(Order[K].toOrdering)) // no need to evaluate fb + else fb.map(fb => map2(fa, fb)(f)) + + override def ap[A, B](ff: SortedMap[K, A => B])(fa: SortedMap[K, A]): SortedMap[K, B] = + ??? //fa.flatMap { case (k, a) => ff.get(k).map(f => (k, f(a))) }(scala.collection.breakOut) + + override def ap2[A, B, Z](f: SortedMap[K, (A, B) => Z])(fa: SortedMap[K, A], fb: SortedMap[K, B]): SortedMap[K, Z] = + ???/*f.flatMap { case (k, f) => + for { a <- fa.get(k); b <- fb.get(k) } yield (k, f(a, b)) + }*/ + + def foldLeft[A, B](fa: SortedMap[K, A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b) { case (x, (k, a)) => f(x, a)} + + def foldRight[A, B](fa: SortedMap[K, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Foldable.iterateRight(fa.values.iterator, lb)(f) + + def tailRecM[A, B](a: A)(f: A => SortedMap[K, Either[A, B]]): SortedMap[K, B] = { + val bldr = SortedMap.newBuilder[K, B](Order[K].toOrdering) + + @tailrec def descend(k: K, either: Either[A, B]): Unit = + either match { + case Left(a) => + f(a).get(k) match { + case Some(x) => descend(k, x) + case None => () + } + case Right(b) => + bldr += ((k, b)) + () + } + + f(a).foreach { case (k, a) => descend(k, a) } + bldr.result + } + + override def size[A](fa: SortedMap[K, A]): Long = fa.size.toLong + + override def get[A](fa: SortedMap[K, A])(idx: Long): Option[A] = + if (idx < 0L || Int.MaxValue < idx) None + else { + val n = idx.toInt + if (n >= fa.size) None + else Some(fa.valuesIterator.drop(n).next) + } + + override def isEmpty[A](fa: SortedMap[K, A]): Boolean = fa.isEmpty + + override def fold[A](fa: SortedMap[K, A])(implicit A: Monoid[A]): A = + A.combineAll(fa.values) + + override def toList[A](fa: SortedMap[K, A]): List[A] = fa.values.toList + } + +} + +trait SortedMapInstances1 { + implicit def catsKernelStdEqForSortedMap[K: Order, V: Eq]: Eq[SortedMap[K, V]] = + new SortedMapEq[K, V] +} + +class SortedMapHash[K, V](implicit V: Hash[V], O: Order[K]) extends SortedMapEq[K, V]()(V, O) with Hash[SortedMap[K, V]] { + // adapted from [[scala.util.hashing.MurmurHash3]], + // but modified standard `Any#hashCode` to `ev.hash`. + import scala.util.hashing.MurmurHash3._ + def hash(x: SortedMap[K, V]): Int = { + var a, b, n = 0 + var c = 1; + x foreach { case (k, v) => + // use the default hash on keys because that's what Scala's Map does + val h = StaticMethods.product2Hash(k.hashCode(), V.hash(v)) + a += h + b ^= h + if (h != 0) c *= h + n += 1 + } + var h = mapSeed + h = mix(h, a) + h = mix(h, b) + h = mixLast(h, c) + finalizeHash(h, n) + } +} + +class SortedMapEq[K, V](implicit V: Eq[V], O: Order[K]) extends Eq[SortedMap[K, V]] { + def eqv(x: SortedMap[K, V], y: SortedMap[K, V]): Boolean = + if (x eq y) true + else x.size == y.size && x.forall { case (k, v1) => + y.get(k) match { + case Some(v2) => V.eqv(v1, v2) + case None => false + } + } +} + +class SortedMapMonoid[K, V](implicit V: Semigroup[V], O: Order[K]) extends Monoid[SortedMap[K, V]] { + + def empty: SortedMap[K, V] = SortedMap.empty(O.toOrdering) + + def combine(xs: SortedMap[K, V], ys: SortedMap[K, V]): SortedMap[K, V] = + if (xs.size <= ys.size) { + xs.foldLeft(ys) { case (my, (k, x)) => + my.updated(k, Semigroup.maybeCombine(x, my.get(k))) + } + } else { + ys.foldLeft(xs) { case (mx, (k, y)) => + mx.updated(k, Semigroup.maybeCombine(mx.get(k), y)) + } + } + + override def combineAll(xss: TraversableOnce[SortedMap[K, V]]): SortedMap[K, V] = { + val acc = mutable.SortedMap.empty[K, V](O.toOrdering) + xss.foreach { m => + val it = m.iterator + while (it.hasNext) { + val (k, v) = it.next + acc(k) = Semigroup.maybeCombine(acc.get(k), v) + } + } + SortedMap.empty[K, V](O.toOrdering) ++ acc + } +} \ No newline at end of file