swiftdictionarynsdictionary

Efficient way to convert a dictionary into another dictionary where all the keys are modified?


In Swift, I have this type of dictionary:

let typingAttributes = [
    NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18),
    NSAttributedString.Key.foregroundColor: UIColor.red,
]

I need to convert it into another dictionary where the key is the rawValue. So something like this:

[
    NSAttributedString.Key.font.rawValue: UIFont.systemFont(ofSize: 18),
    NSAttributedString.Key.foregroundColor.rawValue: UIColor.red,
]

One way I know I can achieve this is by creating a new dictionary, then enumerating all the keys of the original dictionary and setting the values in this new dictionary.

However, is there a better way similar to how Arrays have things like map, reduce etc?


Solution

  • A solution is to use reduce(into:_:):

    let output = typingAttributes.reduce(into: [String: Any]()) { partialResult, tuple in
        let newKey = //Get new key from tuple.key
        partialResult[newKey] = tuple.value
    }
    

    In your case, since you are using NSAttributedString.Key for the dictionary keys, and you want the raw string value:

    let newKey = tuple.key.rawValue
    

    Which can then be simplified into:

    let output = typingAttributes.reduce(into: [String: Any]()) { 
        $0[$1.key.rawValue] = $1.value
    }
    

    With an extension:

    extension Dictionary {
        func mapKeys<T>(_ key: (Key) -> T) -> [T: Value] {
            reduce(into: [T: Value]()) {
                let newKey = key($1.key)
                $0[newKey] = $1.value
            }
        }
    }
    

    Usages:

    let output = typingAttributes.mapKeys { $0.rawValue }
    

    Other samples uses:

    //Convert the keys into their int value (it'd crash if it's not an int)
    let testInt = ["1": "a", "2": "b"].mapKeys { Int($0)! }
    print(testInt)
    
    
    //Keep only the first character of the keys
    let testPrefix = ["123": "a", "234": "b"].mapKeys { String($0.prefix(1)) }
    print(testPrefix)
    
    //Fixing keys into our owns, more "readable" and "Swift friendly for instance
    let keysToUpdate = ["lat": "latitude", "long": "longitude", "full_name": "fullName"]
    let testFixKeys = ["lat": 0.4, "long": 0.5, "full_name": "Seed", "name": "John", "age": 34].mapKeys { keysToUpdate[$0] ?? $0 }
    print(testFixKeys)
    
    //etc.