基于先前值更新Map中的值的惯用方法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于先前值更新Map中的值的惯用方法相关的知识,希望对你有一定的参考价值。
假设我将银行账户信息存储在不可变的Map
中:
val m = Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65)
我想从马克的帐户中提取50美元。我可以这样做:
val m2 = m + ("Mark" -> (m("Mark") - 50))
但这段代码对我来说似乎很难看。有没有更好的方法来写这个?
不幸的是,adjust
API中没有Map
。我有时使用类似下面的函数(以Haskell的Data.Map.adjust
为模型,具有不同的参数顺序):
def adjust[A, B](m: Map[A, B], k: A)(f: B => B) = m.updated(k, f(m(k)))
现在adjust(m, "Mark")(_ - 50)
做你想要的。你还可以使用pimp-my-library pattern来获得更自然的m.adjust("Mark")(_ - 50)
语法,如果你真的想要更干净的东西。
(请注意,如果k
不在地图中,则上面的简短版本会抛出异常,这与Haskell行为不同,可能是您想在实际代码中修复的内容。)
这可以用镜头完成。镜头的想法是能够放大不可变结构的特定部分,并且能够1)从较大的结构中检索较小的部分,或者2)创建具有修改的较小部分的新的较大的结构。在这种情况下,你想要的是#2。
首先,从Lens
偷来的this answer的简单实现,从scalaz偷来的:
case class Lens[A,B](get: A => B, set: (A,B) => A) extends Function1[A,B] with Immutable {
def apply(whole: A): B = get(whole)
def updated(whole: A, part: B): A = set(whole, part) // like on immutable maps
def mod(a: A)(f: B => B) = set(a, f(this(a)))
def compose[C](that: Lens[C,A]) = Lens[C,B](
c => this(that(c)),
(c, b) => that.mod(c)(set(_, b))
)
def andThen[C](that: Lens[B,C]) = that compose this
}
接下来,一个聪明的构造函数,从“更大的结构”Map[A,B]
到“小部分”Option[B]
创建一个镜头。我们通过提供特定密钥来指出我们想要查看的“较小部分”。 (灵感来自我记得的Edward Kmett's presentation on Lenses in Scala):
def containsKey[A,B](k: A) = Lens[Map[A,B], Option[B]](
get = (m:Map[A,B]) => m.get(k),
set = (m:Map[A,B], opt: Option[B]) => opt match {
case None => m - k
case Some(v) => m + (k -> v)
}
)
现在您的代码可以编写:
val m2 = containsKey("Mark").mod(m)(_.map(_ - 50))
注:我实际上改变了mod
从我偷走它的答案,以便它的输入咖喱。这有助于避免额外的类型注释。还要注意_.map
,因为记住,我们的镜头是从Map[A,B]
到Option[B]
。这意味着如果地图不包含密钥"Mark"
,地图将保持不变。否则,这个解决方案最终与特拉维斯提出的adjust
解决方案非常相似。
SO Answer提出另一种选择,使用scalaz的|+|
算子
val m2 = m |+| Map("Mark" -> -50)
|+|
运算符将对现有键的值求和,或在新键下插入值。
启动Scala 2.13
,Map#updatedWith
就是为了这个目的:
// val map = Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65)
map.updatedWith("Mark") {
case Some(money) => Some(money - 50)
case None => None
}
// Map("Mark" -> 50, "Jonathan" -> 350, "Bob" -> 65)
或者以更紧凑的形式:
map.updatedWith("Mark")(_.map(_ - 50))
注意(引用doc)如果重映射函数返回Some(v)
,则使用新值v
更新映射。如果重映射函数返回None
,则删除映射(或者如果最初不存在则保持不存在)。
def updated与[V1>:V](键:K)(重映射功能:(选项[V])=>选项[V1]):映射[K,V1]
这样,我们可以优雅地处理不存在更新值的键的情况:
Map("Jonathan" -> 350, "Bob" -> 65)
.updatedWith("Mark")({ case None => Some(0) case Some(v) => Some(v - 50) })
// Map("Jonathan" -> 350, "Bob" -> 65, "Mark" -> 0)
Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65)
.updatedWith("Mark")({ case None => Some(0) case Some(v) => Some(v - 50) })
// Map("Mark" -> 50, "Jonathan" -> 350, "Bob" -> 65)
Map("Jonathan" -> 350, "Bob" -> 65)
.updatedWith("Mark")({ case None => None case Some(v) => Some(v - 50) })
// Map("Jonathan" -> 350, "Bob" -> 65)
以上是关于基于先前值更新Map中的值的惯用方法的主要内容,如果未能解决你的问题,请参考以下文章