字典扩展:交换键和值 - Swift 4.1

5

字典扩展 - 交换字典键值

Swift 4.1, Xcode 9.3

我想要创建一个函数,它可以接受Dictionary作为参数,并返回该字典的反转版,即将原字典中的值作为新字典的键,原字典中的键作为新字典中对应值。目前,我已经编写了这样一个函数,但是我无法将其制作成Dictionaryextension


我的函数

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
}

用例示例:

 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]

我该如何实现这个理想呢?

1
你无法扩展所有的字典,只有值为可哈希的字典可以被扩展。 - jakehawken
5个回答

8
一个字典的扩展可能看起来像这样,成为键的 value 必须被限制为 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)

@LeoDabus 没错,这对于 OP 来说是一个不错的挑战。我添加了一个 assert 表达式。 - vadian
Set(values).count == count - Leo Dabus
你为什么要使用 assert(condition:, message:) 而不是 precondition(condition:, message:) - Noah Wilder
@NoahWilder 在游乐场上实际上没有什么区别。 - vadian
1
@LeoDabus,这是一个非常好的观点。我会尝试更新它以反映出来。 - jakehawken
显示剩余2条评论

3
如其他答案中已经解释的那样,Value类型必须被限制为Hashable,否则它不能成为新字典的Key
此外,还需要决定如何处理源字典中的重复值。
对于实现,可以将源字典映射到一个键和值互换的序列中,并将其传递给以下初始化程序之一: 这两种方法在处理重复键时有所不同:第一种会中止并抛出运行时异常,第二种会调用闭包以解决冲突。
因此,一个简单的实现方式是:
extension Dictionary where Value: Hashable {

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

采用惰性映射源字典的方式,避免创建一个包含所有交换键/值元组的中间数组。

示例:

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]

如果源字典中有重复的值,这将崩溃。以下是一种变体,可以接受源字典中的重复值(后来的值会覆盖早期的值):
extension Dictionary where Value: Hashable {

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

例子:

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

另一个选项是将其实现为字典初始化器。例如:

extension Dictionary where Value: Hashable {

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

如果源字典中存在重复的值,该函数将会崩溃, 或者作为一个抛出初始化程序

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

或者作为可失败的初始化器:
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
        }
    }
}

示例(用于可失败的初始化程序):

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

或者,如果您确信不存在重复的值:

let dict = [1 : "a", 2 : "b", 3 : "c", 4 : "d", 5 : "e"]
let newDict = Dictionary(swappingKeysAndValues: dict)!

你能再多说一点关于惰性的目的吗? - matt
为什么不让初始化器抛出错误而不是返回nil呢?像这样:`init(swappingKeysAndValues dict: [Value: Key]) throws { try self.init(dict.lazy.map { ($0.value, $0.key) }, uniquingKeysWith: { , in throw DuplicateValuesError() })}` - Leo Dabus
1
@LeoDabus:实际上,在早期版本中我使用了这个方法,但是我将其替换为可失败的初始化器,因为只有一个可能的错误。现在我又添加了它(以及一些其他选项)。感谢您的反馈! - Martin R

2
要明确的是,除非这些值符合 Hashable 协议,否则你所要求的是不可能实现的。因此,你需要一个条件扩展来解决这个问题。
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
    }
}

1
您可以使用 Dictionary(uniqueKeysWithValues:) 初始化器,将字典映射到交换键和值,并创建一个新的字典:
let dict = ["Key1": "Value1", "Key2": "Value2", ...]
let swappedDict = Dictionary(uniqueKeysWithValues: dict.map {(key, value) in return (value, key)})

请注意,Value类型应符合Hashable协议,如果有重复的键,则初始化程序会抛出运行时异常。如果原始字典可能具有重复值,请改用Dictionary(_:uniquingKeysWith:)初始化程序。

-1
替换旧密钥为新的用户定义密钥
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"]

这并不能回答这个问题。问题想要将字典 ["fName": "Robert", "lName": "Jr"] 转换为字典 ["Robert":"fName", "Jr":"lName"] - HangarRash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接