Noah Wilder
Noah Wilder

Reputation: 1574

Dictionary Extension that Swaps Keys & Values - Swift 4.1

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?


Upvotes: 5

Views: 2520

Answers (5)

Sai kumar Reddy
Sai kumar Reddy

Reputation: 1829

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"]

Upvotes: -1

sgl0v
sgl0v

Reputation: 1417

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.

Upvotes: 1

Martin R
Martin R

Reputation: 540005

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)!

Upvotes: 3

vadian
vadian

Reputation: 285220

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)

Upvotes: 8

jakehawken
jakehawken

Reputation: 787

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

Upvotes: 2

Related Questions