更好地创建状态转换以将集合元素添加到Map [K,Set [V]]

时间:2014-10-15 22:28:11

标签: scala scalaz

我有Map[K, Set[V]]我正在使用Scalaz Lenses和State为其添加元素。

到目前为止,我看到自己反复这样做了:

myMapLens.member(key) %= {
    case Some(vals) => Some(vals + newValue)
    case None       => Some(Set(newValue))
}

使用Scalaz有更好的方法吗?每次将我的值设置为Some(...)似乎都很浪费。

具体来说,有没有办法组合Scalaz MapLens和SetLens来实现这个目标?

2 个答案:

答案 0 :(得分:3)

您可以编写一个适配器来#34;展平" Option

import scalaz._, Scalaz._

def noneZero[A: Monoid]: Lens[Option[A], A] = Lens.lensg(_ => Some(_), _.orZero)

这比您需要的更通用,但对您的用例具有相同的行为:

val myMapLens = Lens.lensId[Map[String, Set[Int]]]

val myLens = myMapLens.member("foo").andThen(noneZero).contains(1)

你当然可以使用SetLens上的任何其他方法 - contains只是为了一个很好的演示:

scala> myLens.get(Map("foo" -> Set(1)))
res0: Boolean = true

scala> myLens.get(Map("bar" -> Set(1)))
res1: Boolean = false

scala> myLens.set(Map("foo" -> Set(2)), true)
res2: Map[String,Set[Int]] = Map(foo -> Set(2, 1))

scala> myLens.set(Map("bar" -> Set(2)), true)
res3: Map[String,Set[Int]] = Map(bar -> Set(2), foo -> Set(1))

scala> myLens.set(Map("foo" -> Set(1)), false)
res4: Map[String,Set[Int]] = Map(foo -> Set())

以下可以说是一种稍微更原则的编写适配器的方法:

def noneZero[A: Monoid: Equal]: Lens[Option[A], A] = Lens.lensg(
  _ => a => a.ifEmpty[Option[A]](none)(some(a)),
  _.orZero
)

除外,其行为与相同,取消设置集合中的最后一个值会将其从地图中删除:

scala> myLens.set(Map("foo" -> Set(1)), false)
res5: Map[String,Set[Int]] = Map()

但这可能不是你想要的。

答案 1 :(得分:1)

香草

myMap + (key -> myMap.get(key).fold(Set(newValue))(_ + newValue))

似乎更容易。

编写扩展方法也是如此,为了避免不必要的重建,它值得做一些额外的工作:

implicit class MapsToSetsCanAdd[K,V](map: Map[K, Set[V]]) {
  def setAdd(key: K, value: V) = map.get(key) match {
    case Some(set) => if (set contains value) map else map + (key -> (set + value))
    case None => map + (key -> Set(value))
  }
}

现在你可以愉快myMap setAdd (key, newValue)