使用复杂(双重)操作合并 scalaz 中的地图
Merge maps in scalaz with a complex (double) operation
我正在使用映射将某些值与元组 (Int, Double) 相关联,其中 int 是它们出现的顺序和它们显示的次数的两倍(不是,但更清楚这样使用int和double区分)
棘手的部分是我想为元组的每个元素使用不同的幺半群,对于 int 我想保留最小值,以记住第一次出现,而对于 double 我想使用加法幺半群
所以对于现有密钥,我们有:
val map1 = Map("a" -> (1, 5.0), "b" -> (2, 4.0), "c" -> (3, 8.0))
val map2 = Map("b" -> (4, 1.0))
val merge = map1.toMap |+| map2.toMap
// Map(a -> (1, 5.0), b -> (2, 5.0), c -> (3, 8.0))
对于新密钥,我们有:
val map2 = Map("d" -> (4, 1.0))
val merge2 = map1.toMap |+| map2.toMap
// Map(a -> (1, 5.0), b -> (2, 4.0), c -> (3, 8.0), d -> (4, 1.0))
我找不到办法做到这一点,我显然可以使用加法幺半群,我可以使用最小值,但我看不出如何组合它们。
任何帮助表示赞赏!谢谢
您可以将 scalaz.std.tuple.tuple2Monoid
明确地与您想要的两个幺半群一起使用:
import scalaz.Monoid
implicit val countMonoid: Monoid[(Int, Double)] = scalaz.std.tuple.tuple2Monoid(
Monoid.instance[Int](math.min(_, _), Int.MaxValue),
Monoid.instance[Double](_ + _, 0)
)
然后:
scala> import scalaz.std.map._, scalaz.syntax.monoid._
import scalaz.std.map._
import scalaz.syntax.monoid._
scala> val map1 = Map("a" -> (1, 5.0), "b" -> (2, 4.0), "c" -> (3, 8.0))
map1: scala.collection.immutable.Map[String,(Int, Double)] = Map(a -> (1,5.0), b -> (2,4.0), c -> (3,8.0))
scala> val map2 = Map("b" -> (4, 1.0))
map2: scala.collection.immutable.Map[String,(Int, Double)] = Map(b -> (4,1.0))
scala> val merge = map1.toMap |+| map2.toMap
merge: scala.collection.immutable.Map[String,(Int, Double)] = Map(a -> (1,5.0), b -> (2,5.0), c -> (3,8.0))
scala> val map2 = Map("d" -> (4, 1.0))
map2: scala.collection.immutable.Map[String,(Int, Double)] = Map(d -> (4,1.0))
scala> val merge2 = map1.toMap |+| map2.toMap
merge2: scala.collection.immutable.Map[String,(Int, Double)] = Map(a -> (1,5.0), b -> (2,4.0), c -> (3,8.0), d -> (4,1.0))
不过,这并不是很理想,因为类型 (Int, Double)
可用于表示许多不同的事物,而您刚刚定义了一个幺半群实例,它可能会出现在您或您的地方用户不期望。我个人会用 class 来代替:
case class Count(order: Int, number: Double)
然后在 Count
伴生对象中明确定义实例,或者通过上面的 countMonoid
和 IsoSet[Count, (Int, Double)]
.
我跟随 Travis Brown 并提出了一个围绕案例 class 构建的解决方案,以防止从新的幺半群溢出到每个 (Int, Double)
import scalaz._, Scalaz._, Isomorphism._
import scalaz.Monoid
import scalaz.std.map._, scalaz.syntax.monoid._
case class MonoidFromIsorphism[F, G](iso: F <=> G)(
implicit val G: Monoid[G]
) extends IsomorphismMonoid[F, G]
case class TrafficCount(order: Int, number: Double)
object TrafficCount {
implicit val countMonoid: Monoid[(Int, Double)] = scalaz.std.tuple.tuple2Monoid(
Monoid.instance[Int](math.min(_, _), Int.MaxValue),
Monoid.instance[Double](_ + _, 0)
)
implicit object TrafficCountMonoid extends MonoidFromIsorphism(
new IsoSet[TrafficCount, (Int, Double)] {
def to = (TrafficCount.unapply _) andThen (_.get)
def from = (TrafficCount.apply _).tupled
}
)
}
它按预期工作:
val map1 = Map("a" -> TrafficCount(1, 5.0), "b" -> TrafficCount(2, 4.0), "c" -> TrafficCount(3, 8.0))
val map2 = Map("b" -> TrafficCount(4, 1.0))
val map3 = Map("d" -> TrafficCount(4, 1.0))
scala> val merge = map1.toMap |+| map2.toMap
merge: scala.collection.immutable.Map[String,TrafficCount] = Map(a -> TrafficCount(1,5.0), b -> TrafficCount(2,5.0), c -> TrafficCount(3,8.0))
scala> val merge2 = map1.toMap |+| map2.toMap
merge2: scala.collection.immutable.Map[String,TrafficCount] = Map(a -> TrafficCount(1,5.0), b -> TrafficCount(2,5.0), c -> TrafficCount(3,8.0))
事实上我们可以检查幺半群的零:
scala> mzero[TrafficCount]
res0: TrafficCount = TrafficCount(2147483647,0.0)
它在不应该的地方不起作用:
val map_1 = Map("a" -> (1, 5.0), "b" -> (2, 4.0), "c" -> (3, 8.0))
val map_2 = Map("b" -> (4, 1.0))
val map_3 = Map("d" -> (4, 1.0))
scala> val merge_1 = map_1.toMap |+| map_2.toMap
<console>:38: error: value |+| is not a member of scala.collection.immutable.Map[String,(Int, Double)]
我正在使用映射将某些值与元组 (Int, Double) 相关联,其中 int 是它们出现的顺序和它们显示的次数的两倍(不是,但更清楚这样使用int和double区分)
棘手的部分是我想为元组的每个元素使用不同的幺半群,对于 int 我想保留最小值,以记住第一次出现,而对于 double 我想使用加法幺半群 所以对于现有密钥,我们有:
val map1 = Map("a" -> (1, 5.0), "b" -> (2, 4.0), "c" -> (3, 8.0))
val map2 = Map("b" -> (4, 1.0))
val merge = map1.toMap |+| map2.toMap
// Map(a -> (1, 5.0), b -> (2, 5.0), c -> (3, 8.0))
对于新密钥,我们有:
val map2 = Map("d" -> (4, 1.0))
val merge2 = map1.toMap |+| map2.toMap
// Map(a -> (1, 5.0), b -> (2, 4.0), c -> (3, 8.0), d -> (4, 1.0))
我找不到办法做到这一点,我显然可以使用加法幺半群,我可以使用最小值,但我看不出如何组合它们。 任何帮助表示赞赏!谢谢
您可以将 scalaz.std.tuple.tuple2Monoid
明确地与您想要的两个幺半群一起使用:
import scalaz.Monoid
implicit val countMonoid: Monoid[(Int, Double)] = scalaz.std.tuple.tuple2Monoid(
Monoid.instance[Int](math.min(_, _), Int.MaxValue),
Monoid.instance[Double](_ + _, 0)
)
然后:
scala> import scalaz.std.map._, scalaz.syntax.monoid._
import scalaz.std.map._
import scalaz.syntax.monoid._
scala> val map1 = Map("a" -> (1, 5.0), "b" -> (2, 4.0), "c" -> (3, 8.0))
map1: scala.collection.immutable.Map[String,(Int, Double)] = Map(a -> (1,5.0), b -> (2,4.0), c -> (3,8.0))
scala> val map2 = Map("b" -> (4, 1.0))
map2: scala.collection.immutable.Map[String,(Int, Double)] = Map(b -> (4,1.0))
scala> val merge = map1.toMap |+| map2.toMap
merge: scala.collection.immutable.Map[String,(Int, Double)] = Map(a -> (1,5.0), b -> (2,5.0), c -> (3,8.0))
scala> val map2 = Map("d" -> (4, 1.0))
map2: scala.collection.immutable.Map[String,(Int, Double)] = Map(d -> (4,1.0))
scala> val merge2 = map1.toMap |+| map2.toMap
merge2: scala.collection.immutable.Map[String,(Int, Double)] = Map(a -> (1,5.0), b -> (2,4.0), c -> (3,8.0), d -> (4,1.0))
不过,这并不是很理想,因为类型 (Int, Double)
可用于表示许多不同的事物,而您刚刚定义了一个幺半群实例,它可能会出现在您或您的地方用户不期望。我个人会用 class 来代替:
case class Count(order: Int, number: Double)
然后在 Count
伴生对象中明确定义实例,或者通过上面的 countMonoid
和 IsoSet[Count, (Int, Double)]
.
我跟随 Travis Brown 并提出了一个围绕案例 class 构建的解决方案,以防止从新的幺半群溢出到每个 (Int, Double)
import scalaz._, Scalaz._, Isomorphism._
import scalaz.Monoid
import scalaz.std.map._, scalaz.syntax.monoid._
case class MonoidFromIsorphism[F, G](iso: F <=> G)(
implicit val G: Monoid[G]
) extends IsomorphismMonoid[F, G]
case class TrafficCount(order: Int, number: Double)
object TrafficCount {
implicit val countMonoid: Monoid[(Int, Double)] = scalaz.std.tuple.tuple2Monoid(
Monoid.instance[Int](math.min(_, _), Int.MaxValue),
Monoid.instance[Double](_ + _, 0)
)
implicit object TrafficCountMonoid extends MonoidFromIsorphism(
new IsoSet[TrafficCount, (Int, Double)] {
def to = (TrafficCount.unapply _) andThen (_.get)
def from = (TrafficCount.apply _).tupled
}
)
}
它按预期工作:
val map1 = Map("a" -> TrafficCount(1, 5.0), "b" -> TrafficCount(2, 4.0), "c" -> TrafficCount(3, 8.0))
val map2 = Map("b" -> TrafficCount(4, 1.0))
val map3 = Map("d" -> TrafficCount(4, 1.0))
scala> val merge = map1.toMap |+| map2.toMap
merge: scala.collection.immutable.Map[String,TrafficCount] = Map(a -> TrafficCount(1,5.0), b -> TrafficCount(2,5.0), c -> TrafficCount(3,8.0))
scala> val merge2 = map1.toMap |+| map2.toMap
merge2: scala.collection.immutable.Map[String,TrafficCount] = Map(a -> TrafficCount(1,5.0), b -> TrafficCount(2,5.0), c -> TrafficCount(3,8.0))
事实上我们可以检查幺半群的零:
scala> mzero[TrafficCount]
res0: TrafficCount = TrafficCount(2147483647,0.0)
它在不应该的地方不起作用:
val map_1 = Map("a" -> (1, 5.0), "b" -> (2, 4.0), "c" -> (3, 8.0))
val map_2 = Map("b" -> (4, 1.0))
val map_3 = Map("d" -> (4, 1.0))
scala> val merge_1 = map_1.toMap |+| map_2.toMap
<console>:38: error: value |+| is not a member of scala.collection.immutable.Map[String,(Int, Double)]