【问题标题】:How do I "append" to an immutable dictionary in Swift?如何在 Swift 中“附加”到不可变字典?
【发布时间】:2016-07-13 04:20:12
【问题描述】:

在 Scala 中,immutable.Map 上的 + (k -> v) 运算符返回一个新的 immutable.Map,其中包含原始内容以及新的键/值对。同样,在 C# 中,ImmutableDictionary.add(k, v) 返回一个新的、更新的 ImmutableDictionary。

然而,在 Swift 中,Dictionary 似乎只有可变的 updateValue(v, forKey: k) 函数和可变的 [k:v] 运算符。

我想也许我可以和flatten() 玩一些把戏,但没有运气:

let updated = [original, [newKey: newValue]].flatten()

了解我

Cannot convert value of type '() -> FlattenCollection<[[String : AnyObject]]>' 
to specified type '[String : AnyObject]'

如何从现有的内容创建一个新的、修改后的不可变 Dictionary?


更新:基于this answer's note that Swift dictionaries is value types,以及this answer's mutable version,我想出了以下扩展运算符,但我不是对此感到兴奋——似乎必须有一个更干净的开箱即用替代方案。

func + <K, V>(left: [K:V], right: [K:V]) -> [K:V] {
    var union = left
    for (k, v) in right {
        union[k] = v
    }
    return union
} 

但也许事实(如果我理解正确的话)Swift 字典的不变性是对let 的编译器检查,而不是不同实现类的问题,这意味着这是可以做到的最好的吗?


更新 #2: 如 Jules's answer 中所述,修改未专门优化以在副本之间共享状态的不可变字典(因为 Swift 字典不是)会带来性能问题。对于我当前的用例(AttributedString 属性字典,它往往很小),它仍然可以简化某些值得做的事情,但除非 Swift 实现共享状态不可变字典,否则它可能不是一个好主意一般情况——这是不将其作为内置功能的一个很好的理由。

【问题讨论】:

  • 看起来有人正在建立一个图书馆来做到这一点github.com/tLewisII/ImStructures(免责声明:我没有审查它。)
  • Swift 字典的不变性是编译器检查 [...] 不同的实现类 这是错误的。字典被实现为结构,这意味着它们(基本上)通过副本传递。存储在 let 变量中的任何结构值都是不可变的,因此您不能在其上调用 mutating 方法,因为它们会更改值。如果您将字典(任何结构)分配给 var 变量,它将被复制!然后,您将拥有一个具有新内存位置的不同字典。

标签: swift functional-programming immutability


【解决方案1】:

不幸的是,这是一个很好的问题,因为答案是“你不能”。无论如何,还没有——其他人同意应该添加这个,因为有一个Swift Evolution proposal for this (and some other missing Dictionary features)。它目前处于“等待审核”状态,因此您可能会看到 merged() 方法,它基本上是您在 Swift 未来版本中的 + 运算符!

与此同时,您可以使用您的解决方案来附加整个字典,或者一次附加一个值:

extension Dictionary {
    func appending(_ key: Key, _ value: Value) -> [Key: Value] {
        var result = self
        result[key] = value
        return result
    }
}

【讨论】:

  • 链接到提案的好主意,但merge 并不是 OP 所追求的。
  • 不完全是,但它与他对flatten() 的尝试很接近:它将允许let newDictionary = oldDictionary.merged([new: stuff]),这对我来说已经足够了。 (有趣的是,这也会让他现有的flatten() 技巧发挥作用。)
  • @jtbandes 不是,但因为它与单个键值对的 +(tuple: (K, V)) 一样简洁,所以它可能已经足够好了,特别是如果实现足够聪明以达到 O(log n) .
  • @andyvn22 我认为不应该添加它,因为我们正在处理不可变数据。鉴于此,该操作应创建一个新字典。
  • 它确实创建了一个新字典!与其他集合类型一样,该提案将添加一对方法,一个用于可变字典,一个用于常量字典。
【解决方案2】:

目前没有内置方法可以执行此操作。您可以使用扩展程序编写自己的代码(如下)。

但请记住,这可能会复制字典,因为字典是写时复制的,而您正在这样做(制作副本,然后对其进行变异)。你可以通过首先使用一个可变变量来避免这一切:-)

extension Dictionary {
    func updatingValue(_ value: Value, forKey key: Key) -> [Key: Value] {
        var result = self
        result[key] = value
        return result
    }
}

let d1 = ["a": 1, "b": 2]
d1  // prints ["b": 2, "a": 1]
let d2 = d1.updatingValue(3, forKey: "c")
d1  // still prints ["b": 2, "a": 1]
d2  // prints ["b": 2, "a": 1, "c": 3]

【讨论】:

    【解决方案3】:

    最直接的做法是复制到一个变量,修改,然后重新分配回一个常量:

    var updatable = original
    updatable[newKey] = newValue
    let updated = updatable
    

    显然不漂亮,但它可以很容易地包装成一个函数。

    extension Dictionary { 
        func addingValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> { 
            // Could add a guard here to enforce add not update, if needed 
            var updatable = self
            updatable[key] = value 
            return updatable
        } 
    }
    
    let original = [1 : "One"]
    let updated = original.addingValue("Two", forKey: 2)
    

    我认为除了自己动手之外没有其他解决方案。

    但也许事实(如果我理解正确的话)Swift 字典的不变性是对let 的编译器检查

    对,可变性是在storage,即变量上指定的,而不是在value上。

    【讨论】:

      【解决方案4】:

      不要尝试更新不可变字典,除非它是专门为不可变而设计的。

      不可变字典通常使用一种数据结构(例如具有不可变节点的红/黑树,而不是可以在实例之间共享或类似的),该结构可以生成修改后的副本,而无需复制整个内容,但只复制一个子集(即它们有 O(log(n)) 复制和修改操作)但是大多数为可变系统设计然后与不可变接口一起使用的字典没有,所以有 O(n) 复制和修改操作.当您的字典开始超过几百个节点时,您会真正注意到性能差异。

      【讨论】:

      • 在这种特殊情况下,我正在查看 n ≪ 100,但会注意到警告。从其他 cmets/answers 听起来好像 Swift 不可变字典确实存在这个问题。
      猜你喜欢
      • 2015-06-10
      • 2021-04-24
      • 2015-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-28
      • 2016-06-11
      • 2015-08-30
      相关资源
      最近更新 更多