【问题标题】:Nice way to add number to element in Scala map if key exists or insert new element it not如果键存在或不插入新元素,则在 Scala 映射中向元素添加数字的好方法
【发布时间】:2015-02-07 20:34:12
【问题描述】:

我知道几个类似的问题。他们对我没有帮助 - 如果没有现有密钥,代码将无法工作。

我只需要一些不错的方法来附加 Map 并将其添加到现有键(如果它确实存在)或作为新键(如果映射不包含适当的键)。

以下代码有效,但我不喜欢它:

val a = collection.mutable.Map(("k1" -> 1), ("k2" -> 5))
val key = "k1"

val elem = a.get(key)
if (elem == None) {
    a += ("k5" -> 200)
} else {
    a.update(key, elem.get + 5)
}

有什么更好的点吗? 当前的 Scala 版本是 2.10.4,我目前无法切换到 2.11。 可变映射不是 100% 的限制,而是首选。

例如,这里是similar question,但我还需要考虑不存在的不存在密钥的情况。至少我们应该理解a.get(key) 可以是None 或者添加一些更好的方法。 |+| 是个好主意,但我想保留基本的 Scala 2.10.x。

【问题讨论】:

    标签: scala dictionary containers


    【解决方案1】:

    这样做的最短方法:

    a += a.get(key).map(x => key -> (x + 5)).getOrElse("k5" -> 200)
    

    一般:

    a += a.get(k).map(f).map(k -> _).getOrElse(kv)
    

    如果您的字典是不可变的,则相同:

    m + m.get(k).map(f).map(k -> _).getOrElse(kv)
    

    所以我看不出有任何理由在这里使用可变集合。

    如果你不喜欢Option.map 的所有这些东西:

    m + (if (m.contains(k)) k -> f(m(k)) else kv)
    

    请注意,存在一整类可能的变化:

    k1 -> f(m(k1)) else k2 -> v2 //original
    k1 -> f(m(k1)) else k1 -> v2
    k1 -> f(m(k2)) else k2 -> v2
    k1 -> f(m(k2)) else k1 -> v2
    k2 -> v2 else k1 -> f(m(k1)) 
    k1 -> v2 else k1 -> f(m(k1))
    k2 -> v2 else k1 -> f(m(k2))
    k1 -> v2 else k1 -> f(m(k2))
    ... //v2 may also be a function from some key's value
    

    那么,为什么它不是标准功能? IMO,因为所有变体仍然可以作为单行实现。如果您想要具有所有功能的库,可以将其实现为单行,您知道,它是 Scalaz :)。

    附:如果你还想知道为什么没有“update(d) if persist”功能 - 请参阅@Rex Kerr 的回答 here

    【讨论】:

    • @VasyaNovikov 这在理论上是正确的(对于一些实际情况也是如此),同时,如果您查看我引用的 Rex Kerr 的答案,您会发现更改发生的频率要高于常见情况的初始化。不过,话虽如此,如果您针对特定的高性能可变集合 - 我实际上会完全避免使用 scala 集合库(至少 2.x),而是使用 java std 库,特别是如果线程安全(使用 CAS 和等等)是必需的。
    • 哦,我想你是对的。我误解了主要问题本身。我认为这是关于懒惰地创建地图,但从不更新已经存在的条目。对不起,我的错。
    【解决方案2】:

    您可以为此创建自己的函数:

    def addOrUpdate[K, V](m: collection.mutable.Map[K, V], k: K, kv: (K, V), 
                          f: V => V) {
      m.get(k) match {
        case Some(e) => m.update(k, f(e))
        case None    => m += kv
      }
    }
    
    addOrUpdate(a, "k1", "k5" -> 200, (v: Int) => v + 5)
    

    【讨论】:

    • 现在可能是最好的选择。但是为什么 Scala 复杂的容器库不能提供如此简单的开箱即用的东西 - 让键和值要么增加值(如果不存在键),要么用当前值和我的值做一些事情?下次肯定会把这个至少收录到企业库中(魔鬼)。
    • 恕我直言,这很奇怪:如果 other 不存在,则添加一个密钥并不是真正的主流行为
    • 为什么?例如我想通过键形成聚合作为地图,我需要一个一个地添加元素,所以不能使用reduce()之类的东西?
    【解决方案3】:

    Scala 2.13 introduced updatedWith method 这似乎是根据密钥的存在有条件地更新地图的最惯用方式。

    val a = Map(("k1" -> 1), ("k2" -> 5))
    
    val a1 = a.updatedWith("k1") { 
      case Some(v) => Some(v + 5)
      case None => Some(200) 
    }
    println(a1) // Map(k1 -> 6, k2 -> 5)
    

    也可以使用它删除值:

    val a2 = a.updatedWith("k2") { 
      case Some(5) => None
      case v => v 
    }
    println(a2) // Map(k1 -> 1)
    

    摘自Scala Standard Library reference

    def updatedWith[V1 >: V](key: K)(remappingFunction: (Option[V]) => Option[V1]): Map[K, V1]

    更新指定键的映射及其当前可选映射的值(Some 如果有当前映射,None 如果没有)。

    如果重映射函数返回Some(v),则映射将更新为新值v。如果重新映射函数返回 None,则删除映射(如果最初不存在,则保持不存在)。如果函数本身抛出异常,则重新抛出异常,并且当前映射保持不变。

    【讨论】:

      【解决方案4】:

      一种比较清晰的方法:

      val a = collection.mutable.Map[String, Int]() withDefault insertNewValue
      
      def insertNewValue(key: String): Int =
        a += key -> getValueForKey(key)
        a(key)
      }
      
      def getValueForKey(key: String): Int = key.length
      

      不过,我完全不鼓励使用可变集合。最好将内部可变状态保留为包含不可变字段的变量。

      这是因为有一条简单的规则,除非完全有必要,否则您不应该暴露您的内部状态,如果这样做,您应该尽可能减少可能产生的潜在副作用。

      如果您公开一个可变状态的引用,任何其他参与者都可以更改它的值,从而失去引用透明度。 所有对可变集合的引用都很长且难以使用,这并非偶然。这是开发者给你的信息

      毫不奇怪,代码仍然保持不变,在地图实例化时有一些微小的变化。

      var a = Map[String, Int]() withDefault insertNewValue
      
      def insertNewValue(key: String): Int = {
        a += key -> getValueForKey(key)
        a(key)
      }
      
      def getValueForKey(key: String): Int = key.length 
      

      【讨论】:

        【解决方案5】:

        对于你的可变地图,你可以这样做:

        val a = collection.mutable.Map(("k1" -> 1), ("k2" -> 5))
        val key = "k1"
        
        a.update(key, a.getOrElse(key, 0) + 5)
        
        a  // val res1: scala.collection.mutable.Map[String,Int] = HashMap(k1 -> 6, k2 -> 5)
        

        【讨论】:

        猜你喜欢
        • 2020-10-28
        • 2015-12-03
        • 2020-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-04
        • 1970-01-01
        相关资源
        最近更新 更多