我如何 "append" 到 Swift 中的不可变字典?

How do I "append" to an immutable dictionary in Swift?

在 Scala 中,+ (k -> v) operator on immutable.Map returns a new immutable.Map with the contents of the original, plus the new key/value pair. Similarly, in C#, ImmutableDictionary.add(k, v) returns 一个新的、更新的 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 are value types, and this answer 的可变版本,我想出了以下扩展运算符,但我对此并不兴奋——看起来就像必须有一个更干净的开箱即用的替代品。

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: 所述,修改未专门优化以在副本之间共享状态的不可变字典(如 Swift 字典aren't) 会出现性能问题。对于我当前的用例(AttributedString 属性字典,它往往相当小)它可能仍然简化了某些值得做的事情,但是直到并且除非 Swift 实现共享状态不可变字典它是在一般情况下可能不是一个好主意——这是不将其作为内置功能的一个很好的理由。

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

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

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]

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

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)

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

But maybe the fact (if I understand correctly) that the immutability of Swift dictionaries is a compiler check on let

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

不幸的是,这是一个很好的问题,因为答案是 "you can't"。无论如何,还没有——其他人同意应该添加这个,因为有一个 Swift Evolution proposal for this (and some other missing Dictionary features)。它目前是 "awaiting review",因此您可能会在 Swift!

的未来版本中看到一个 merged() 方法,它基本上是您的 + 运算符

同时,您可以使用您的解决方案追加整个字典,或一次追加一个值:

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

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

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