Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 215
David Moles Avatar asked Jul 13 '16 04:07

David Moles


People also ask

How to append item to dictionary Swift?

To append an element to a dictionary in Swift programming, assign a value to the Dictionary with a key that is not already present. Note that the new_key has to be unique with respect to the keys already present in the dictionary. value does not have any constraints.

How do I copy one dictionary to another in Swift?

In Swift, you have either value types (struct, enum, tuple, array, dict etc) or reference types (classes). If you need to copy a class object, then, you have to implement the methods copyWithZone in your class and then call copy on the object.

Are dictionaries passed by reference Swift?

Instances of your SharedDictionary will be passed-by-reference (not copied).


4 Answers

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
    }
}
like image 50
andyvn22 Avatar answered Oct 23 '22 08:10

andyvn22


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]
like image 41
jtbandes Avatar answered Oct 23 '22 09:10

jtbandes


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.

like image 25
jscs Avatar answered Oct 23 '22 09:10

jscs


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.

like image 22
Jules Avatar answered Oct 23 '22 08:10

Jules