Dictionary Extension that Swaps Keys & Values - Swift 4.1
Asked Answered
C

5

5

Dictionary Extension - Swap Dictionary Keys & Values

Swift 4.1, Xcode 9.3

I want to make a function that would take Dictionary and return said dictionary, but with its values as the keys and its keys as its respective values. So far, I have made a function to do this, but I cannot for the life of me make it into an extension for Dictionary.


My Function

func swapKeyValues<T, U>(of dict: [T : U]) -> [U  : T] {
    let arrKeys = Array(dict.keys)
    let arrValues = Array(dict.values)
    var newDict = [U : T]()
    for (i,n) in arrValues.enumerated() {
        newDict[n] = arrKeys[i]
    }
    return newDict
}

Example of Usage:

 let dict = [1 : "a", 2 : "b", 3 : "c", 4 : "d", 5 : "e"]
 let newDict = swapKeyValues(of: dict)
 print(newDict) //["b": 2, "e": 5, "a": 1, "d": 4, "c": 3]

Ideal:

 let dict = [1 : "a", 2 : "b", 3 : "c", 4 : "d", 5 : "e"]

 //I would like the function in the extension to be called swapped()
 print(dict.swapped()) //["b": 2, "e": 5, "a": 1, "d": 4, "c": 3]

How do I accomplish this ideal?


Coben answered 24/4, 2018 at 16:41 Comment(1)
You wouldn't be able to extend ALL dictionaries. Only dictionaries where the values are hashable.Buckskins
I
8

A extension of Dictionary could look like this, the value which becomes the key must be constrained to Hashable

extension Dictionary where Value : Hashable {

    func swapKeyValues() -> [Value : Key] {
        assert(Set(self.values).count == self.keys.count, "Values must be unique")
        var newDict = [Value : Key]()
        for (key, value) in self {
            newDict[value] = key
        }
        return newDict
    }
}

let dict = [1 : "a", 2 : "b", 3 : "c", 4 : "d", 5 : "e"]
let newDict = dict.swapKeyValues()
print(newDict)
Impart answered 24/4, 2018 at 16:52 Comment(7)
@LeoDabus Right, that's a good challenge for the OP 😁 I added an assert expressionImpart
Set(values).count == countRabush
btw you can use Swift 4's reduce(into:) method: return reduce(into: [:]) { $0[$1.value] = $1.key }Rabush
Why would you use assert(condition:, message:) rather than precondition(condition:, message:)?Coben
@NoahWilder In a Playground there is practically no difference.Impart
@LeoDabus that’s a fantastic point. I’ll try and update it to reflect that.Buckskins
A possible alternative would be to use newDict.updateValue(key, forKey: value) – then the return value indicates if same value occurred previously and you don't have to traverse the source dictionary in advance.Rodney
R
3

As already explained in the other answers, the Value type must be restricted to be Hashable, otherwise it can not be the Key for the new dictionary.

Also one has to decide how duplicate values in the source dictionary should be handled.

For the implementation one can map the source dictionary to a sequence with keys and values exchanged, and passes that to one of the initializers

These differ in how duplicate keys are treated: The first one aborts with a runtime exception, the second one calls the closure for conflict resolution.

So a simple implementation is

extension Dictionary where Value: Hashable {

    func swapKeyValues() -> [Value : Key] {
        return Dictionary<Value, Key>(uniqueKeysWithValues: lazy.map { ($0.value, $0.key) })
    }
}

(Mapping the source dictionary lazily avoids the creation of an intermediate array with all swapped key/value tuples.)

Example:

let dict = [1 : "a", 2 : "b", 3 : "c", 4 : "d", 5 : "e"]
print(dict.swapKeyValues()) //["b": 2, "e": 5, "a": 1, "d": 4, "c": 3]

This will crash if the source dictionary has duplicate values. Here is a variant which accepts duplicate values in the source dictionary (and “later” values overwrite “earlier” ones):

extension Dictionary where Value: Hashable {

    func swapKeyValues() -> [Value : Key] {
        return Dictionary<Value, Key>(lazy.map { ($0.value, $0.key) }, uniquingKeysWith: { $1 })
    }
}

Example:

let dict = [1 : "a", 2 : "b", 3 : "b"]
print(dict.swapKeyValues()) // ["b": 3, "a": 1]

Another option would be to implement this as a dictionary initializer. For example:

extension Dictionary where Value: Hashable {

    init?(swappingKeysAndValues dict: [Value:  Key]) {
        self.init(uniqueKeysWithValues: dict.lazy.map( { ($0.value, $0.key) }))
    }
}

which crashes in the case of duplicate values in the source dictionary, or as a throwing initializer

extension Dictionary where Value: Hashable {

    struct DuplicateValuesError: Error, LocalizedError {
        var errorDescription: String? {
            return "duplicate value"
        }
    }

    init(swappingKeysAndValues dict: [Value:  Key]) throws {
            try self.init(dict.lazy.map { ($0.value, $0.key) },
                          uniquingKeysWith: { _,_ in throw DuplicateValuesError() })
    }
}

or as a failable initializer:

extension Dictionary where Value: Hashable {

    struct DuplicateValuesError: Error { }

    init?(swappingKeysAndValues dict: [Value:  Key]) {
        do {
            try self.init(dict.lazy.map { ($0.value, $0.key) },
                          uniquingKeysWith: { _,_ in throw DuplicateValuesError() })
        } catch {
            return nil
        }
    }
}

Example (for the failable initializer):

let dict = [1 : "a", 2 : "b", 3 : "c", 4 : "d", 5 : "e"]
if let newDict = Dictionary(swappingKeysAndValues: dict) {
    print(newDict) //["b": 2, "e": 5, "a": 1, "d": 4, "c": 3]
}

Or, if you are sure that no duplicate values occur:

let dict = [1 : "a", 2 : "b", 3 : "c", 4 : "d", 5 : "e"]
let newDict = Dictionary(swappingKeysAndValues: dict)!
Rodney answered 24/4, 2018 at 18:48 Comment(3)
Can you say a little more about the purpose of the laziness?Olympia
Why don't you make the initializer throw the error instead of returning nil ? Something like init(swappingKeysAndValues dict: [Value: Key]) throws { try self.init(dict.lazy.map { ($0.value, $0.key) }, uniquingKeysWith: { _,_ in throw DuplicateValuesError() }) }Rabush
@LeoDabus: Actually I had than in an earlier revision, but I replaced it with a failable initializer because there is only a single possible error. – I have added it (and some more options) again. Thanks for the feedback!Rodney
B
2

To be clear, what you're asking for is impossible unless the values conform to the Hashable protocol. So, a conditional extension is what you're looking for.

extension Dictionary where Value: Hashable {
    func keyValueSwapped() -> [Value:Key] {
        var newDict = [Value:Key]()
        keys.forEach { (key) in
            let value = self[key]!
            newDict[value] = key
        }
        return newDict
    }
}
Buckskins answered 24/4, 2018 at 16:48 Comment(0)
U
1

You can map a dictionary to swap keys and values and create a new one using Dictionary(uniqueKeysWithValues:) initializer:

let dict = ["Key1": "Value1", "Key2": "Value2", ...]
let swappedDict = Dictionary(uniqueKeysWithValues: dict.map {(key, value) in return (value, key)})

Just take into account that Value type should conform to the Hashable protocol and initializer throws a runtime exception in case of duplicate keys. If original dictionary might have duplicate values, use the Dictionary(_:uniquingKeysWith:) initializer instead.

Utrillo answered 8/9, 2020 at 12:39 Comment(0)
A
-1

Replacing old key with new user define key

extension Dictionary {
    mutating func switchKey(fromKey: Key, toKey: Key) {
        if let entry = removeValue(forKey: fromKey) {
            self[toKey] = entry
        }
    }
}

var person: [String: String] = ["fName": "Robert", "lName": "Jr"]
person.switchKey(fromKey: "fName", toKey: "firstname")
person.switchKey(fromKey: "lName", toKey: "lastname")
print(person) // ["firstname": "Robert", "lastname": "Jr"]
Argyle answered 5/6, 2023 at 10:56 Comment(1)
This does not answer the question. The question wants to change the dictionary ["fName": "Robert", "lName": "Jr"] to the dictionary ["Robert":"fName", "Jr":"lName"].Wolk

© 2022 - 2024 — McMap. All rights reserved.