通过复杂(双重)操作在scalaz中合并地图

时间:2018-12-14 12:15:56

标签: scala scalaz scalaz7

我正在使用地图将某些值与元组(Int,Double)相关联,其中int是它们出现的顺序,并且是它们显示次数的两倍(它不是,但是使用int和加倍以区分)

棘手的部分是我想对元组的每个元素使用不同的monoid,对于int来说,我想保留最小值,记住第一个外观,而对于double而言,我想使用加法monoid 因此,对于现有密钥,我们有:

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))

我找不到解决办法,很明显我可以使用加成的monoid,也可以使用minval的,但是我看不到如何组合它们。 任何帮助表示赞赏!谢谢

2 个答案:

答案 0 :(得分:1)

您可以将scalaz.std.tuple.tuple2Monoid显式地与所需的两个Monoid一起使用:

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)可用于表示许多不同的事物,并且您刚刚定义了一个monoid实例,该实例可能会出现在您或您的用户所不喜欢的地方没想到。我个人将使用案例类:

case class Count(order: Int, number: Double)

然后在Count伴随对象中,通过显式或通过上面的countMonoidIsoSet[Count, (Int, Double)]定义实例。

答案 1 :(得分:0)

我跟随特拉维斯·布朗(Travis Brown),并提出了一个围绕案例类构建的解决方案,以防止从新的monoid溢出到每个(整数,双精度)

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))

事实上,我们可以检查monoid的零:

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)]