简体   繁体   中英

How do I “append” to an immutable dictionary in Swift?

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 .

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

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

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.

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?


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.

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!

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
    }
}

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]

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

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

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 (ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM