How can I update all Dictionary entries in place?

I need to iterate through all the existing key-value pairs in a Dictionary. Is it safe to iterate through Dictionary.keys and call Dictionary.updateValue(_: forKey:) if I will neither add nor remove a pair. Will calling updateValue cause keys to split off a useless copy of the dictionary? (Like using DefaultIndices for indices will split off standard collections if I mutate the element while iterating.)

1 Like

I believe that Dictionary.keys will capture a reference to the dictionary itself, and therefore you are better off using a for loop such as:

for (key, value) in dict {
  ...
}

He’s also trying to mutate dict. Looping like that will capture dict (to run for-loop), so mutating inside the loop would cause COW🐮-ing (if not access violation).

Does mapValues(_:) not work for your need?

The general approach for mutating a collection should work though, namely to manually advance an index:

var i = dict.startIndex

while i < dict.endIndex {
  let (key, value) = dict[i]
  dict.updateValue(/*...*/, forKey: key)
  dict.formIndex(after: &i)
}

That isn’t in-place, but IIRC, I believe we are moving away from creating mutating/non-mutating versions of every operation, in favour of optimisations that will reuse existing storage when it is able to do so.

I don’t think that’s been implemented yet, and personally I’m sceptical, but I believe I remember some of the Apple engineers mentioning it and they seem to believe it can be done, so... we’ll see. It would be extremely cool if it worked.

That's what I'm doing now as a workaround; mapping from x to (x + 1) % m in my closure. But this unnecessarily creates a brand new dictionary, which I then copy on top of the current one. Even if @Karl's speculation comes true and there's some slick optimization to get rid of the extra work, it would still be (looking) logically wrong.

Collections that can't conform to MutableCollection and/or RangeReplaceableCollection can still have mutating methods inspired by them to alter element content and/or collection composition. Such methods for Dictionary all have a RRC tinge to them, even the ones that could be used for MC purposes. The problem is the Dictionary is missing a primitive, Value mutation (but not insertion/removal) via subscript, and that's a bug I should file soon. Here's what I mean:

extension Dictionary {

    /// Accesses only the value at the specified position.
    ///
    /// This subscript takes an index into the dictionary, instead of a key, and
    /// returns the only the `value` part of the corresponding key-value tuple.
    /// When performing collection-based operations that return an index into a
    /// dictionary, use this subscript with the resulting value to possibly
    /// mutate the value without risk of inserting or removing a key-value pair.
    ///
    /// For example, to find the key for a particular value in a dictionary, use
    /// the `firstIndex(where:)` method.
    ///
    ///     let countryCodes = ["BR": "Brazil", "GH": "Ghana", "JP": "japan"]
    ///     if let index = countryCodes.firstIndex(where: { $0.value == "japan" }) {
    ///         countryCodes[valueAt: index] = "Japan"
    ///         print(countryCodes[index])
    ///         print("Japan's country code is '\(countryCodes[index].key)'.")
    ///     } else {
    ///         print("Didn't find 'Japan' as a value in the dictionary.")
    ///     }
    ///     // Prints "("JP", "Japan")"
    ///     // Prints "Japan's country code is 'JP'."
    ///
    /// - Parameter position: The position of the key-value pair to partially
    ///   access.  `position` must be a valid index of the dictionary and not
    ///   equal to `endIndex`.
    /// - Returns: The value part of the key-value pair corresponding to
    ///   `position`, but in `inout` mode.
    public subscript(valueAt i: Index) -> Value {
        get { return self[i].value }
        set { /* ??? */ }
    }

    /// If the given key is found in the dictionary, use the given closure to
    /// possibly mutate the corresponding value.
    ///
    /// For example, to ensure the country codes for Japan and China are in
    /// uppercase, if the corresponding country is in the dictionary:
    ///
    ///     let codeCountry = ["Brazil": "BR", "Ghana": "GH", "Japan": "jp"]
    ///     let uppercase: (inout String) -> Void = { $0 = $0.uppercased() }
    ///     var hadZhKey = codeCountry.updateValue(forKey: "China", by: uppercase)
    ///     var hadJpKey = codeCountry.updateValue(forKey: "Japan", by: uppercase)
    ///     print(hadZhKey, hadJpKey, codeCountry["Japan"])
    ///     // Prints 'false true "JP"'
    ///
    /// - Parameter key: The key of the desired element.
    /// - Parameter body: A closure that takes the `value` member from an
    ///   element of the collection as a mutable parameter.
    /// - Parameter value: The mutable `value` member of the targeted element.
    /// - Returns: `true` if the `key` was found, `false` otherwise.
    ///
    /// - Postcondition: If the `key` was found, `body` was called on the
    ///   corresponding `value` and any changes on that value are retained.
    @discardableResult
    @inlinable
    public mutating func updateValue(forKey key: Key, by body: (_ value: inout Value) throws -> Void) rethrows -> Bool {
        guard let keyIndex = index(forKey: key) else { return false }

        try body(&self[valueAt: keyIndex])
        return true
    }

    /// Calls the given closure on each key-value pair in the dictionary in the
    /// same order as a `for`-`in` loop, with an opportunity to mutate the
    /// value.
    ///
    /// The two loops in the following example produce the same output:
    ///
    ///     let wordNumbers = ["one": 1, "two": 2, "three": 3]
    ///     let wordNumbers2 = wordNumbers.mapValues { 2 * $0 }
    ///     print(wordNumbers2 == ["one": 2, "two": 4, "three": 6])
    ///     // Prints "true"
    ///
    ///     var wordNumbersTwo = wordNumbers
    ///     wordNumbersTwo.updateEach { key, value in
    ///         value *= 2
    ///     }
    ///     print(wordNumbersTwo == ["one": 2, "two": 4, "three": 6])
    ///     // Same as above
    ///
    /// Using the `updateEach` method is distinct from a `for`-`in` loop in
    /// several important ways:
    ///
    /// 1. You cannot use a `break` or `continue` statement to exit the current
    ///    call of the `body` closure or skip subsequent calls.
    /// 2. Using the `return` statement in the `body` closure will exit only from
    ///    the current call to `body`, not from any outer scope, and won't skip
    ///    subsequent calls.
    /// 3. You can change the `value` member, without risk of disrupting any
    ///    retention `keys` may use when looping over all key-value pairs by
    ///    ensuring none are added nor removed (unlike iteration with `keys` and
    ///    using `updateValue(_: forKey:)`).
    ///
    /// - Parameter body: A closure that takes an element of the sequence as a
    ///   parameter.
    /// - Parameter key: The immutable key of the currently visited key-value
    ///   element pair.
    /// - Parameter value: The mutable value of the currently visited key-value
    ///   element pair.
    ///
    /// - Postcondition: Any mutations to any element's value are retained.
    public mutating func updateEach(by body: (_ key: Key, _ value: inout Value) throws -> Void) rethrows {
        var index = startIndex
        let end = endIndex
        while index < end {
            let key = self[index].key
            try body(key, &self[valueAt: index])
            formIndex(after: &index)
        }
    }

}

Dictionary.updateEach(by:) is what I first came up with, because that's what I needed for my work. I couldn't figure out how to implement it; that's when I realized we're missing a primitive. Staring at the various subscript members and their mutability, I realized we need a set-able subscript for values, then the implementation of updateEach became clear, and a need for the intermediate updateValue(forKey: by:).

I'm still looking through the Dictionary implementation files to see if operations there can provide code for the new user-level primitive.

...

The updateEach loop is the same as what's needed for a mutating forEach.

...

Glancing over the just-referenced thread: woah, the values partial accessor conforms to MutableCollection?!... Yes, it's that way in the current Git repo. I wonder if we could use that code somehow; there should still be a more direct way for a dictionary to mutate a value.

I tried to find the relevant posts, but this is the only one I could find:

I'm pretty sure I've heard this from others, too, though - that we're trying to move away from having copying/mutating variants of every collection algorithm (can you imagine, like for all the String transformations? formUppercased/formLowercased/formCapitalized/etc).

I do think making the index subscript settable is a worthwhile change, though.

I had hope, but when I looked at one of the other mutating-forEach threads:

So we need a set-able Value subscript directly on Dictionary.

What? No, the post of mine that you linked includes the proper way to iterate while mutating, namely to manually advance an index rather than using indices.

The problem isn't using manual index advancement, but that there's no subscript that can access an element's value for mutation that doesn't include an insertion/removal semantic. (In other words, the main subscript is read-only, and the other subscripts are RRC-like, but I want a MC-only-like subscript.) You can mutate through the values wrapping accessor, but it has the same COW effect as using default implementations of indices.

Is it possible you're thinking about mutating the wrong thing. You're not mutating the dictionary, you're mutating the items. So make a mutating method for Int (assuming there isn't already one I'm unaware of):

var myDictionary = ["1":1, "2":2]

extension Int {
    mutating func advance(by value: Int) {
        self = self + value
    }
}

myDictionary.keys.forEach{ myDictionary[$0]?.advance(by: 1) }
print(myDictionary) // prints ["1": 2, "2": 3]
Terms of Service

Privacy Policy

Cookie Policy