Объединить карты в скаляр со сложной (двойной) операцией - PullRequest
0 голосов
/ 14 декабря 2018

Я использую карту, чтобы связать определенные значения с кортежем (Int, Double), где int - это порядок, в котором они появились, и удвоенное количество раз, которое они показывают (это не так, но это более понятно, используя int иудваивается, чтобы различить)

Сложность в том, что я хочу использовать разные моноиды для каждого элемента кортежа, для int я хочу сохранить значение min, чтобы запомнить первое появление, а для 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))

Я не могу найти способ сделать это, я могуочевидно, использовать моноид сложения, и я могу использовать минвал, но я не вижу, как их объединить.Любая помощь приветствуется!спасибо

Ответы [ 2 ]

0 голосов
/ 16 декабря 2018

Я последовал за Тревисом Брауном и пришел с решением, основанным на классе дел, чтобы предотвратить вторичный переход от нового моноида к каждому (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)]
0 голосов
/ 14 декабря 2018

Вы можете явно использовать 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) может использоваться для представления множества разных вещей, и вы только что определили моноидный экземпляр, который может появляться в местах, которые вы или ваши пользователи не ожидаете.Лично я бы вместо этого использовал класс case:

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

, а затем определил экземпляр в объекте-компаньоне Count, либо явно, либо через countMonoid и выше IsoSet[Count, (Int, Double)].

...