以前的值为基础,在地图中更新值的惯用方法

12 浏览
0 Comments

以前的值为基础,在地图中更新值的惯用方法

假设我将银行账户信息存储在一个不可变的Map中:

val m = Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65)

我想从Mark的账户中提取50美元。我可以这样做:

val m2 = m + ("Mark" -> (m("Mark") - 50))

但是我觉得这段代码很丑陋。有更好的写法吗?

0
0 Comments

问题的原因是Map API中缺少了adjust方法,而原作者希望实现类似于Haskell的Data.Map.adjust的功能。解决方法是定义一个adjust函数,用于更新Map中指定key的value,并返回更新后的Map。

首先,定义adjust函数的实现如下:

def adjust[A, B](m: Map[A, B], k: A)(f: B => B) = m.updated(k, f(m(k)))

然后,使用adjust函数进行更新操作,例如:adjust(m, "Mark")(_ - 50),这样就可以实现更新Map中指定key的value的功能。

此外,还提到了另一种更简洁的语法,即使用pimp-my-library模式实现:m.adjust("Mark")(_ - 50)

需要注意的是,以上实现只适用于Map中存在指定key的情况。如果要忽略不存在的key,则可以使用map.get(k).fold(map)(b => map.updated(k, f(b)));如果想要在key不存在时设置该key的value,则可以使用f: Option[B] => B的方法。

最后,原作者表示adjust方法非常有用,并希望能够将其加入到标准库中。

0
0 Comments

从Scala 2.13版本开始,可以使用Map的updatedWith方法来更新Map中的值。该方法的作用是根据先前的值更新Map中的值。

updatedWith方法的使用方式如下:

val map = Map("Mark" -> 100, "Jonathan" -> 350, "Bob" -> 65)
map.updatedWith("Mark") {
  case Some(money) => Some(money - 50)
  case None        => None
}

或者更简洁的形式:

map.updatedWith("Mark")(_.map(_ - 50))

需要注意的是,如果remapping函数返回Some(v),则更新Map中的值为v。如果remapping函数返回None,则删除该映射(如果一开始不存在则保持不存在)。

通过updatedWith方法,我们可以优雅地处理需要更新值的键不存在的情况。例如:

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)

在Scala 2.13及以上版本中,使用updatedWith方法是解决更新Map中值的最正确的方法。

0
0 Comments

问题的出现原因是需要在Map中根据先前的值更新值的需求。解决方法是使用lenses。lens的概念是能够放大不可变结构的特定部分,并能够1)从较大的结构中检索较小的部分,或者2)创建一个包含修改后的较小部分的新较大结构。在这种情况下,你所需要的是第二种方法。

首先,从这个答案中(来自scalaz)窃取了一个简单的Lens实现:

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]的智能构造函数来创建一个lens。我们通过提供特定的键来指示我们想要查看的“较小部分”(受到Edward Kmett在Scala中lenses演讲的启发):

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,因为请记住,我们的lens是从Map[A,B]到Option[B]的。这意味着,如果Map不包含键"Mark",则Map将保持不变。否则,该解决方案与Travis提出的adjust解决方案非常相似。

0