From ea4023d07911e6f1b20ab399c47e946973a76e0e Mon Sep 17 00:00:00 2001 From: Daniel Urban Date: Sat, 21 Dec 2024 21:06:10 +0100 Subject: [PATCH] Improve Map.Extra --- build.sbt | 3 + .../main/scala/dev/tauri/choam/data/Map.scala | 14 +++- .../dev/tauri/choam/data/SimpleMap.scala | 19 +++++ .../tauri/choam/data/SimpleOrderedMap.scala | 41 +++++++++-- .../scala/dev/tauri/choam/data/mapSpec.scala | 70 ++++++++++++++++--- 5 files changed, 130 insertions(+), 17 deletions(-) diff --git a/build.sbt b/build.sbt index 78dbda930..a252d1515 100644 --- a/build.sbt +++ b/build.sbt @@ -317,6 +317,9 @@ lazy val data = crossProject(JVMPlatform, JSPlatform) ProblemFilters.exclude[MissingClassProblem]("dev.tauri.choam.data.Ttrie$State"), // private ProblemFilters.exclude[MissingClassProblem]("dev.tauri.choam.data.Ttrie$Value"), // private ProblemFilters.exclude[MissingClassProblem]("dev.tauri.choam.data.Ttrie$Value$"), // private + ProblemFilters.exclude[ReversedMissingMethodProblem]("dev.tauri.choam.data.Map#Extra.keys"), // sealed + ProblemFilters.exclude[ReversedMissingMethodProblem]("dev.tauri.choam.data.Map#Extra.valuesUnsorted"), // sealed + ProblemFilters.exclude[ReversedMissingMethodProblem]("dev.tauri.choam.data.Map#Extra.items"), // sealed ), ) diff --git a/data/shared/src/main/scala/dev/tauri/choam/data/Map.scala b/data/shared/src/main/scala/dev/tauri/choam/data/Map.scala index 2455b10be..ad9c6402f 100644 --- a/data/shared/src/main/scala/dev/tauri/choam/data/Map.scala +++ b/data/shared/src/main/scala/dev/tauri/choam/data/Map.scala @@ -19,6 +19,7 @@ package dev.tauri.choam package data import cats.kernel.{ Hash, Order } +import cats.data.Chain import cats.effect.kernel.{ Ref => CatsRef } import cats.effect.std.MapRef @@ -51,8 +52,19 @@ sealed trait Map[K, V] { self => object Map extends MapPlatform { sealed trait Extra[K, V] extends Map[K, V] { + + // TODO: type Snapshot + // TODO: def snapshot: Axn[Snapshot] + def clear: Axn[Unit] - def values(implicit V: Order[V]): Axn[Vector[V]] + + def values(implicit V: Order[V]): Axn[Vector[V]] // TODO:0.5: remove this + + def keys: Axn[Chain[K]] + + def valuesUnsorted: Axn[Chain[V]] // TODO:0.5: rename to `values` + + def items: Axn[Chain[(K, V)]] } private[data] trait UnsealedMap[K, V] extends Map[K, V] diff --git a/data/shared/src/main/scala/dev/tauri/choam/data/SimpleMap.scala b/data/shared/src/main/scala/dev/tauri/choam/data/SimpleMap.scala index 147287ea1..d39da67cd 100644 --- a/data/shared/src/main/scala/dev/tauri/choam/data/SimpleMap.scala +++ b/data/shared/src/main/scala/dev/tauri/choam/data/SimpleMap.scala @@ -21,6 +21,7 @@ package data import scala.collection.immutable.{ Map => ScalaMap } import cats.kernel.{ Hash, Order } +import cats.data.Chain import cats.collections.HashMap private final class SimpleMap[K, V] private ( @@ -98,6 +99,24 @@ private final class SimpleMap[K, V] private ( } } + final override def keys: Axn[Chain[K]] = { + repr.get.map { hm => + Chain.fromIterableOnce(hm.keysIterator) + } + } + + final override def valuesUnsorted: Axn[Chain[V]] = { + repr.get.map { hm => + Chain.fromIterableOnce(hm.valuesIterator) + } + } + + final override def items: Axn[Chain[(K, V)]] = { + repr.get.map { hm => + Chain.fromIterableOnce(hm.iterator) + } + } + override def refLike(key: K, default: V): RefLike[V] = new RefLike[V] { final def get: Axn[V] = diff --git a/data/shared/src/main/scala/dev/tauri/choam/data/SimpleOrderedMap.scala b/data/shared/src/main/scala/dev/tauri/choam/data/SimpleOrderedMap.scala index 4201bb153..a4a59753d 100644 --- a/data/shared/src/main/scala/dev/tauri/choam/data/SimpleOrderedMap.scala +++ b/data/shared/src/main/scala/dev/tauri/choam/data/SimpleOrderedMap.scala @@ -21,6 +21,7 @@ package data import scala.collection.immutable.{ Map => ScalaMap } import cats.kernel.Order +import cats.data.Chain import cats.collections.AvlMap import dev.tauri.choam.Rxn import dev.tauri.choam.RefLike @@ -92,11 +93,41 @@ private final class SimpleOrderedMap[K, V] private ( final override def values(implicit V: Order[V]): Axn[Vector[V]] = { repr.get.map { am => - val vb = Vector.newBuilder[V] - am.foldLeft(()) { (_, kv) => - vb += kv._2 - } - vb.result() + val b = scala.collection.mutable.ArrayBuffer.newBuilder[V] + b.sizeHint(am.set.size) + am.foldLeft(b) { (b, kv) => + b += kv._2 + }.result().sortInPlace()(V.toOrdering).toVector + } + } + + final override def keys: Axn[Chain[K]] = { + repr.get.map { m => + Chain.fromSeq( + m.foldLeft(Vector.newBuilder[K]) { (vb, kv) => + vb.addOne(kv._1) + }.result() + ) + } + } + + final override def valuesUnsorted: Axn[Chain[V]] = { + repr.get.map { m => + Chain.fromSeq( + m.foldLeft(Vector.newBuilder[V]) { (vb, kv) => + vb.addOne(kv._2) + }.result() + ) + } + } + + final override def items: Axn[Chain[(K, V)]] = { + repr.get.map { m => + Chain.fromSeq( + m.foldLeft(Vector.newBuilder[(K, V)]) { (vb, kv) => + vb.addOne(kv) + }.result() + ) } } diff --git a/data/shared/src/test/scala/dev/tauri/choam/data/mapSpec.scala b/data/shared/src/test/scala/dev/tauri/choam/data/mapSpec.scala index 1d2f09613..d78c07160 100644 --- a/data/shared/src/test/scala/dev/tauri/choam/data/mapSpec.scala +++ b/data/shared/src/test/scala/dev/tauri/choam/data/mapSpec.scala @@ -21,6 +21,7 @@ package data import scala.collection.immutable.{ Set => ScalaSet } import cats.kernel.{ Hash, Order } +import cats.data.Chain import cats.effect.SyncIO import org.scalacheck.effect.PropF @@ -36,16 +37,23 @@ final class MapSpec_SimpleOrdered_ThreadConfinedMcas_SyncIO with SpecThreadConfinedMcas with MapSpecSimpleOrdered[SyncIO] -trait MapSpecSimpleHash[F[_]] extends MapSpec[F] { this: McasImplSpec => - - override type MyMap[K, V] = Map.Extra[K, V] +trait MapSpecSimpleHash[F[_]] extends MapSpecSimple[F] { this: McasImplSpec => def mkEmptyMap[K : Hash : Order, V]: F[Map.Extra[K, V]] = Map.simpleHashMap[K, V].run[F] +} - // TODO: these should run for simpleOrderedMap too +trait MapSpecSimpleOrdered[F[_]] extends MapSpecSimple[F] { this: McasImplSpec => - test("Map.Extra should perform clear correctly") { + def mkEmptyMap[K : Hash : Order, V]: F[Map.Extra[K, V]] = + Map.simpleOrderedMap[K, V].run[F] +} + +trait MapSpecSimple[F[_]] extends MapSpec[F] { this: McasImplSpec => + + override type MyMap[K, V] = Map.Extra[K, V] + + test("Map.Extra should perform `clear` correctly") { for { m <- mkEmptyMap[Int, String] _ <- (Rxn.pure(42 -> "foo") >>> m.put).run[F] @@ -61,7 +69,7 @@ trait MapSpecSimpleHash[F[_]] extends MapSpec[F] { this: McasImplSpec => } yield () } - test("Map.Extra should perform values correctly") { + test("Map.Extra should perform `values` correctly") { for { m <- mkEmptyMap[Int, String] _ <- assertResultF(m.values.run[F], Vector.empty) @@ -76,14 +84,54 @@ trait MapSpecSimpleHash[F[_]] extends MapSpec[F] { this: McasImplSpec => _ <- assertEqualsF(v2, Vector("abc", "xyz")) } yield () } -} -trait MapSpecSimpleOrdered[F[_]] extends MapSpec[F] { this: McasImplSpec => + test("Map.Extra should perform `keys` correctly") { + for { + m <- mkEmptyMap[Int, String] + _ <- assertResultF(m.keys.run[F], Chain.empty) + _ <- (Rxn.pure(42 -> "foo") >>> m.put).run[F] + _ <- (Rxn.pure(99 -> "bar") >>> m.put).run[F] + v1 <- m.keys.run[F] + _ <- assertEqualsF(v1.iterator.toSet, ScalaSet(42, 99)) + _ <- (Rxn.pure(99 -> "xyz") >>> m.put).run[F] + _ <- (Rxn.pure(128 -> "abc") >>> m.put).run[F] + _ <- m.del[F](42) + v2 <- m.keys.run[F] + _ <- assertEqualsF(v2.iterator.toSet, ScalaSet(99, 128)) + } yield () + } - override type MyMap[K, V] = Map.Extra[K, V] + test("Map.Extra should perform `valuesUnsorted` correctly") { + for { + m <- mkEmptyMap[Int, String] + _ <- assertResultF(m.valuesUnsorted.run[F], Chain.empty) + _ <- (Rxn.pure(42 -> "foo") >>> m.put).run[F] + _ <- (Rxn.pure(99 -> "bar") >>> m.put).run[F] + v1 <- m.valuesUnsorted.run[F] + _ <- assertEqualsF(v1.iterator.toSet, ScalaSet("bar", "foo")) + _ <- (Rxn.pure(99 -> "xyz") >>> m.put).run[F] + _ <- (Rxn.pure(128 -> "abc") >>> m.put).run[F] + _ <- m.del[F](42) + v2 <- m.valuesUnsorted.run[F] + _ <- assertEqualsF(v2.iterator.toSet, ScalaSet("abc", "xyz")) + } yield () + } - def mkEmptyMap[K : Hash : Order, V]: F[Map.Extra[K, V]] = - Map.simpleOrderedMap[K, V].run[F] + test("Map.Extra should perform `items` correctly") { + for { + m <- mkEmptyMap[Int, String] + _ <- assertResultF(m.items.run[F], Chain.empty) + _ <- (Rxn.pure(42 -> "foo") >>> m.put).run[F] + _ <- (Rxn.pure(99 -> "bar") >>> m.put).run[F] + v1 <- m.items.run[F] + _ <- assertEqualsF(v1.iterator.toSet, ScalaSet(42 -> "foo", 99 -> "bar")) + _ <- (Rxn.pure(99 -> "xyz") >>> m.put).run[F] + _ <- (Rxn.pure(128 -> "abc") >>> m.put).run[F] + _ <- m.del[F](42) + v2 <- m.items.run[F] + _ <- assertEqualsF(v2.iterator.toSet, ScalaSet(128 -> "abc", 99 -> "xyz")) + } yield () + } } trait MapSpec[F[_]]