如何在Swift中“追加”到一个不变的字典?

时间:2022-09-18 22:00:51

In Scala, the + (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 a new, updated ImmutableDictionary.

在Scala中,+ (k -> v)操作符是不可变的。Map返回一个新的不可变的。映射与原始内容,加上新的键/值对。同样,在c#中,ImmutableDictionary。add(k, v)返回一个新的更新的ImmutableDictionary。

In Swift, however, Dictionary appears only to have the mutating updateValue(v, forKey: k) function and the mutating [k:v] operator.

然而,在Swift中,Dictionary只出现了突变updateValue(v, forKey: k)函数和突变[k:v]操作符。

I thought maybe I could play some trick with flatten(), but no luck:

我想也许我可以用flatten()来玩个恶作剧,但是运气不佳:

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

gets me

让我

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

How do I create a new, modified immutable Dictionary from the contents of an existing one?

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


Update: Based on this answer's note that Swift dictionaries are value types, and this answer's mutable version, I came up with the following extension operator, but I'm not excited about it -- it seems like there must be a cleaner out-of-the-box alternative.

更新:基于这个答案,Swift字典是值类型,而这个答案是可变的,我提出了以下扩展操作符,但我对此并不感兴趣——似乎必须有一种更干净的开箱即用的替代方法。

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

But maybe the fact (if I understand correctly) that the immutability of Swift dictionaries is a compiler check on let rather than a matter of different implementation classes means this is the best that can be done?

但是,也许事实(如果我正确理解),Swift字典的不变性是一个编译器检查,而不是一个不同的实现类的问题,这意味着这是最好的方法了吗?


Update #2: As noted in Jules's answer, modifying immutable dictionaries that aren't specifically optimized to share state between copies (as Swift dictionaries aren't) presents performance problems. For my current use case (AttributedString attribute dictionaries, which tend to be fairly small) it may still simplify certain things enough to be worth doing, but until and unless Swift implements a shared-state immutable dictionary it's probably not a good idea in the general case -- which is a good reason not to have it as a built-in feature.

更新#2:正如Jules的回答所指出的,修改那些没有特别优化以在副本之间共享状态的不可变字典(斯威夫特字典没有)会带来性能问题。为我现在的用例(AttributedString属性字典,它往往是相当小的)它仍可能简化某些事情足以值得去做,但除非迅速实现了共享状态不变的字典很可能不是一个好主意在一般情况下,这是一个很好的理由不把它作为一个内置的特性。

4 个解决方案

#1


5  

Unfortunately, this is a good question because the answer is "you can't". Not yet, anyway--others agree this should be added, because there's a Swift Evolution proposal for this (and some other missing Dictionary features). It's currently "awaiting review", so you may see a merged() method that's basically your + operator in a future version of Swift!

不幸的是,这是个好问题,因为答案是“你不能”。反正还没有——其他人也同意应该添加这个功能,因为有一个快速进化的建议(以及一些其他丢失的字典功能)。它目前正在“等待审查”,所以您可能会看到一个合并()方法,它基本上是您的+操作符在未来版本的Swift!

In the meantime, you can use your solution to append entire dictionaries, or for one value at a time:

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

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

#2


2  

There's no built-in way to do this right now. You could write your own using an extension (below).

现在还没有内置的方法。您可以使用扩展名(如下)编写自己的扩展名。

But keep in mind that this will likely copy the dictionary, because dictionaries are copy-on-write, and you're doing exactly that (making a copy, then mutating it). You can avoid all this by just using a mutable variable in the first place :-)

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

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


2  

The most straightforward thing to do is to copy to a variable, modify, then re-assign back to a constant:

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

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

Not pretty, obviously, but it could be wrapped into a function easily enough.

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

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)

I don't believe there's a solution other than roll-your-own.

我认为除了自己动手,没有别的解决办法。

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

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

Right, mutability is specified on the storage, that is, the variable, not on the value.

对,可变性是在存储上指定的,即变量,而不是在值上。

#4


1  

Do not try to update an immutable dictionary unless it has been specifically designed for immutability.

不要试图更新一个不可变的字典,除非它是专门为不变性设计的。

Immutable dictionaries usually use a data structure (such as a red/black tree with immutable nodes than can be shared between instances or similar) that can generate a modified copy without needing to make copies of the entire content, but only a subset (i.e. they have O(log(n)) copy-and-modify operations) but most dictionaries that are designed for a mutable system and then used with an immutable interface do not, so have O(n) copy-and-modify operations. When your dictionary starts to get larger than a few hundred nodes, you'll really notice the performance difference.

不变的字典通常使用一个数据结构(如红/黑树与不可改变的节点之间可以共享实例或类似),可以生成一个修改后的副本,而不需要复制整个内容,但只有一个子集(即他们有O(log(n))复制并修改操作)但大多数字典为可变的系统设计,然后使用一个不可变的接口不这样做,所以有O(n)复制并修改操作。当您的字典开始变得大于几百个节点时,您将真正注意到性能差异。

#1


5  

Unfortunately, this is a good question because the answer is "you can't". Not yet, anyway--others agree this should be added, because there's a Swift Evolution proposal for this (and some other missing Dictionary features). It's currently "awaiting review", so you may see a merged() method that's basically your + operator in a future version of Swift!

不幸的是,这是个好问题,因为答案是“你不能”。反正还没有——其他人也同意应该添加这个功能,因为有一个快速进化的建议(以及一些其他丢失的字典功能)。它目前正在“等待审查”,所以您可能会看到一个合并()方法,它基本上是您的+操作符在未来版本的Swift!

In the meantime, you can use your solution to append entire dictionaries, or for one value at a time:

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

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

#2


2  

There's no built-in way to do this right now. You could write your own using an extension (below).

现在还没有内置的方法。您可以使用扩展名(如下)编写自己的扩展名。

But keep in mind that this will likely copy the dictionary, because dictionaries are copy-on-write, and you're doing exactly that (making a copy, then mutating it). You can avoid all this by just using a mutable variable in the first place :-)

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

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


2  

The most straightforward thing to do is to copy to a variable, modify, then re-assign back to a constant:

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

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

Not pretty, obviously, but it could be wrapped into a function easily enough.

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

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)

I don't believe there's a solution other than roll-your-own.

我认为除了自己动手,没有别的解决办法。

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

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

Right, mutability is specified on the storage, that is, the variable, not on the value.

对,可变性是在存储上指定的,即变量,而不是在值上。

#4


1  

Do not try to update an immutable dictionary unless it has been specifically designed for immutability.

不要试图更新一个不可变的字典,除非它是专门为不变性设计的。

Immutable dictionaries usually use a data structure (such as a red/black tree with immutable nodes than can be shared between instances or similar) that can generate a modified copy without needing to make copies of the entire content, but only a subset (i.e. they have O(log(n)) copy-and-modify operations) but most dictionaries that are designed for a mutable system and then used with an immutable interface do not, so have O(n) copy-and-modify operations. When your dictionary starts to get larger than a few hundred nodes, you'll really notice the performance difference.

不变的字典通常使用一个数据结构(如红/黑树与不可改变的节点之间可以共享实例或类似),可以生成一个修改后的副本,而不需要复制整个内容,但只有一个子集(即他们有O(log(n))复制并修改操作)但大多数字典为可变的系统设计,然后使用一个不可变的接口不这样做,所以有O(n)复制并修改操作。当您的字典开始变得大于几百个节点时,您将真正注意到性能差异。