Scala 基于以前的值更新映射中的值的惯用方法
假设我将银行账户信息存储在一个不可变的Scala 基于以前的值更新映射中的值的惯用方法,scala,map,immutability,Scala,Map,Immutability,假设我将银行账户信息存储在一个不可变的映射中: val m = Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65) 我想从马克的账户上取50美元。我可以这样做: val m2 = m + ("Mark" -> (m("Mark") - 50)) 但我觉得这个代码很难看。有没有更好的方法写这个 不幸的是,MapAPI中没有adjust。我有时会使用如下函数(以Haskell为模型,参数顺序不同): 现在adjust(m,
映射中
:
val m = Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65)
我想从马克的账户上取50美元。我可以这样做:
val m2 = m + ("Mark" -> (m("Mark") - 50))
但我觉得这个代码很难看。有没有更好的方法写这个 不幸的是,
Map
API中没有adjust
。我有时会使用如下函数(以Haskell为模型,参数顺序不同):
现在adjust(m,“Mark”)(-50)
做你想做的事。如果您确实想要更干净的东西,您还可以使用来获得更自然的m.adjust(“Mark”)(-50)
语法
(请注意,如果k
不在映射中,上面的简短版本会引发异常,这与Haskell行为不同,可能是您希望在真实代码中修复的情况。)这可以通过镜头来完成。镜头的理念就是能够放大不可变结构的特定部分,并且能够1)从较大结构中检索较小的部分,或者2)使用修改过的较小部分创建新的较大结构。在这种情况下,你想要的是#2
首先,一个简单的Lens
,偷自,偷自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
}
接下来,一个智能构造器将镜头从“较大结构”映射到“较小部分”选项[B]
。我们通过提供一个特定的键来指示要查看的“较小部分”。(灵感来源于我的记忆):
现在可以编写代码:
val m2 = containsKey("Mark").mod(m)(_.map(_ - 50))
n、 b.我实际上改变了我偷来的答案的mod
,这样它就接受了它的输入。这有助于避免额外的类型注释。还要注意\uu.map
,因为请记住,我们的镜头是从映射[A,B]
到选项[B]
。这意味着如果地图不包含键“Mark”
,则地图将保持不变。否则,此解决方案与Travis提出的adjust
解决方案非常相似。An提出了另一种替代方案,使用scalaz的+
操作符
val m2 = m |+| Map("Mark" -> -50)
|+
运算符将对现有键的值求和,或在新键下插入值。启动Scala 2.13
,用于此确切目的:
// 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))
请注意,(引用)如果重新映射函数返回
Some(v)
,则映射将用新值v
更新。如果重新映射函数返回None
,则映射将被删除(如果最初不存在,则映射将保持不存在)
def更新为[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。获取(k)。折叠(map)(b= > map(更新)(k,f(b))< /C>如果你想忽略一个丢失的键,或者一个有<代码> f的选项:选项[b]=b/代码>如果你想在它不存在的时候设置键。@ AdnnFISH:调整(M,马克)((.GETORSER(0)-50))非常有用-我希望<代码>调整< /C> >在标准库中!(可能是@adamnfish的折叠变体…)这似乎是给定scala 2.13中最正确的解决方案+
map.updatedWith("Mark")(_.map(_ - 50))
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)