Какой самый чистый способ применения map() для словаря в Swift?

Я хотел бы сопоставить функцию со всеми ключами в словаре. Я надеялся, что что-то вроде следующего будет работать, но фильтр не может быть применен непосредственно к словарю. Какой самый чистый способ достичь этого?

В этом примере я пытаюсь увеличить каждое значение на 1. Однако это случайное для примера - основная цель - выяснить, как применить map() к словарю.

var d = ["foo" : 1, "bar" : 2]

d.map() {
    $0.1 += 1
}

Ответ 1

Свифт 4+

Хорошие новости! Swift 4 включает mapValues(_:) который создает копию словаря с теми же ключами, но разными значениями. Он также включает перегрузку filter(_:) которая возвращает Dictionary, и init(uniqueKeysWithValues:) и init(_:uniquingKeysWith:) для создания Dictionary из произвольной последовательности кортежей. Это означает, что если вы хотите изменить и ключи, и значения, вы можете сказать что-то вроде:

let newDict = Dictionary(uniqueKeysWithValues:
    oldDict.map { key, value in (key.uppercased(), value.lowercased()) })

Есть также новые API для объединения словарей вместе, замены значения по умолчанию для отсутствующих элементов, группировки значений (преобразования коллекции в словарь массивов, основанный на результате сопоставления коллекции какой-либо функции) и многое другое.

Во время обсуждения предложения SE-0165, в котором были представлены эти функции, я несколько раз поднимал этот ответ о переполнении стека, и я думаю, что огромное количество голосов помогло продемонстрировать спрос. Так что спасибо за вашу помощь, чтобы сделать Swift лучше!

Ответ 2

С Swift 5 вы можете использовать один из пяти следующих фрагментов, чтобы решить вашу проблему.


# 1. Использование Dictionary mapValues(_:) метод

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.mapValues { value in
    return value + 1
}
//let newDictionary = dictionary.mapValues { $0 + 1 } // also works

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

# 2. Использование метода Dictionary map и init(uniqueKeysWithValues:)

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let tupleArray = dictionary.map { (key: String, value: Int) in
    return (key, value + 1)
}
//let tupleArray = dictionary.map { ($0, $1 + 1) } // also works

let newDictionary = Dictionary(uniqueKeysWithValues: tupleArray)

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

# 3. Использование Dictionary метод reduce(_:_:) или reduce(into:_:) метод reduce(into:_:)

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.reduce([:]) { (partialResult: [String: Int], tuple: (key: String, value: Int)) in
    var result = partialResult
    result[tuple.key] = tuple.value + 1
    return result
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.reduce(into: [:]) { (result: inout [String: Int], tuple: (key: String, value: Int)) in
    result[tuple.key] = tuple.value + 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

# 4. Использование Dictionary subscript(_:default:) индекс

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

var newDictionary = [String: Int]()
for (key, value) in dictionary {
    newDictionary[key, default: value] += 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

# 5. Использование Dictionary subscript(_:) нижний индекс

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

var newDictionary = [String: Int]()
for (key, value) in dictionary {
    newDictionary[key] = value + 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

Ответ 3

В то время как большинство ответов здесь сосредоточено на том, как сопоставить весь словарь (ключи и значения), вопрос действительно хотел только сопоставить значения. Это важное различие, поскольку значения отображения позволяют гарантировать одинаковое количество записей, тогда как отображение как ключа, так и значения может привести к дублированию ключей.

Здесь есть расширение, mapValues, которое позволяет отображать только значения. Обратите внимание, что он также расширяет словарь с помощью init из последовательности пар ключ/значение, что немного более общее, чем инициализация его из массива:

extension Dictionary {
    init<S: SequenceType where S.Generator.Element == Element>
      (_ seq: S) {
        self.init()
        for (k,v) in seq {
            self[k] = v
        }
    }

    func mapValues<T>(transform: Value->T) -> Dictionary<Key,T> {
        return Dictionary<Key,T>(zip(self.keys, self.values.map(transform)))
    }

}

Ответ 4

Самый чистый способ - просто добавить map в словарь:

extension Dictionary {
    mutating func map(transform: (key:KeyType, value:ValueType) -> (newValue:ValueType)) {
        for key in self.keys {
            var newValue = transform(key: key, value: self[key]!)
            self.updateValue(newValue, forKey: key)
        }
    }
}

Проверка работы:

var dic = ["a": 50, "b": 60, "c": 70]

dic.map { $0.1 + 1 }

println(dic)

dic.map { (key, value) in
    if key == "a" {
        return value
    } else {
        return value * 2
    }
}

println(dic)

Вывод:

[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]

Ответ 5

Вы также можете использовать reduce вместо карты. reduce способен делать что-либо map может и многое другое!

let oldDict = ["old1": 1, "old2":2]

let newDict = reduce(oldDict, [String:Int]()) { dict, pair in
    var d = dict
    d["new\(pair.1)"] = pair.1
    return d
}

println(newDict)   //  ["new1": 1, "new2": 2]

Было бы довольно легко обернуть это в расширение, но даже без расширения он позволяет делать то, что вы хотите, с помощью одного вызова функции.

Ответ 6

Оказывается, вы можете это сделать. Что вам нужно сделать, так это создать массив из MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>, возвращаемого из словаря keys. (Информация здесь) Затем вы можете сопоставить этот массив и передать обновленные значения обратно в словарь.

var dictionary = ["foo" : 1, "bar" : 2]

Array(dictionary.keys).map() {
    dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}

dictionary

Ответ 7

Согласно Стандартной справочной библиотеке Swift, карта является функцией массивов. Не для словарей.

Но вы можете перебирать словарь для изменения ключей:

var d = ["foo" : 1, "bar" : 2]

for (name, key) in d {
    d[name] = d[name]! + 1
}

Ответ 8

Я искал способ сопоставить словарь прямо в типизированном массиве с пользовательскими объектами. Нашел решение в этом расширении:

extension Dictionary {
    func mapKeys<U> (transform: Key -> U) -> Array<U> {
        var results: Array<U> = []
        for k in self.keys {
            results.append(transform(k))
        }
        return results
    }

    func mapValues<U> (transform: Value -> U) -> Array<U> {
        var results: Array<U> = []
        for v in self.values {
            results.append(transform(v))
        }
        return results
    }

    func map<U> (transform: Value -> U) -> Array<U> {
        return self.mapValues(transform)
    }

    func map<U> (transform: (Key, Value) -> U) -> Array<U> {
        var results: Array<U> = []
        for k in self.keys {
            results.append(transform(k as Key, self[ k ]! as Value))
        }
        return results
    }

    func map<K: Hashable, V> (transform: (Key, Value) -> (K, V)) -> Dictionary<K, V> {
        var results: Dictionary<K, V> = [:]
        for k in self.keys {
            if let value = self[ k ] {
                let (u, w) = transform(k, value)
                results.updateValue(w, forKey: u)
            }
        }
        return results
    }
}

Используя его как следует:

self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in
    return VDLFilterValue(name: key, amount: value)
})

Ответ 9

Swift 3

Я стараюсь легко в Swift 3.

Я хочу сопоставить [String: String?] до [String: String], я использую forEach вместо карты или плоской карты.

    let oldDict = ["key0": "val0", "key1": nil, "key1": "val2","key2": nil]
    var newDict = [String: String]()
    oldDict.forEach { (source: (key: String, value: String?)) in
        if let value = source.value{
            newDict[source.key] = value
        }
    }

Ответ 10

Я предполагаю, что проблема заключается в том, чтобы сохранить ключи нашего оригинального словаря и каким-то образом отобразить все его значения.

Давайте обобщим и скажем, что мы можем сопоставить все значения с другим типом.

Итак, мы хотели бы начать с словаря и закрытия (функция) и в конечном итоге с новым словарем. Проблема, конечно, в том, что строгая типизация Swift мешает. Закрытие должно указывать, какой тип входит и какой тип выходит, и поэтому мы не можем сделать метод, который принимает общее закрытие, за исключением контекста общего. Таким образом, нам нужна общая функция.

Другие решения сосредоточились на том, чтобы делать это в общем мире самой общей генерации словаря, но мне легче думать с точки зрения функции верхнего уровня. Например, мы могли бы написать это, где K - это тип ключа, V1 - тип значения стартового словаря, а V2 - тип значения словаря:

func mapValues<K,V1,V2>(d1:[K:V1], closure:(V1)->V2) -> [K:V2] {
    var d2 = [K:V2]()
    for (key,value) in zip(d1.keys, d1.values.map(closure)) {
        d2.updateValue(value, forKey: key)
    }
    return d2
}

Вот простой пример его вызова, просто чтобы доказать, что generic действительно разрешает себя во время компиляции:

let d : [String:Int] = ["one":1, "two":2]
let result = mapValues(d) { (i : Int) -> String in String(i) }

Мы начали со словаря [String:Int] и закончили со словарем [String:String], преобразуя значения первого словаря через замыкание.

(EDIT: теперь я вижу, что это фактически то же самое, что и решение AirspeedVelocity, за исключением того, что я не добавлял лишнюю элегантность инициализатора словаря, который делает словарь из zip-последовательности.)

Ответ 11

Swift 3

Использование:

let bob = ["a": "AAA", "b": "BBB", "c": "CCC"]
bob.mapDictionary { ($1, $0) } // ["BBB": "b", "CCC": "c", "AAA": "a"]

Extension:

extension Dictionary {
    func mapDictionary(transform: (Key, Value) -> (Key, Value)?) -> Dictionary<Key, Value> {
        var dict = [Key: Value]()
        for key in keys {
            guard let value = self[key], let keyValue = transform(key, value) else {
                continue
            }

            dict[keyValue.0] = keyValue.1
        }
        return dict
    }
}

Ответ 12

Другой подход заключается в map в словаре и reduce, где функции keyTransform и valueTransform являются функциями.

let dictionary = ["a": 1, "b": 2, "c": 3]

func keyTransform(key: String) -> Int {
    return Int(key.unicodeScalars.first!.value)
}

func valueTransform(value: Int) -> String {
    return String(value)
}

dictionary.map { (key, value) in
     [keyTransform(key): valueTransform(value)]
}.reduce([Int:String]()) { memo, element in
    var m = memo
    for (k, v) in element {
        m.updateValue(v, forKey: k)
    }
    return m
}

Ответ 13

Я пытаюсь только сопоставить значения (потенциально изменяя их тип), включая это расширение:

extension Dictionary {
    func valuesMapped<T>(_ transform: (Value) -> T) -> [Key: T] {
        var newDict = [Key: T]()
        for (key, value) in self {
            newDict[key] = transform(value)
        }
        return newDict
    }
}

Учитывая, что у вас есть этот словарь:

let intsDict = ["One": 1, "Two": 2, "Three": 3]

Преобразование значений одной строки выглядит следующим образом:

let stringsDict = intsDict.valuesMapped { String($0 * 2) }
// => ["One": "2", "Three": "6", "Two": "4"]

Преобразование значений нескольких строк выглядит следующим образом:

let complexStringsDict = intsDict.valuesMapped { (value: Int) -> String in
    let calculationResult = (value * 3 + 7) % 5
    return String("Complex number #\(calculationResult)")
}
// => ["One": "Complex number #0", "Three": ...

Ответ 14

Swift 3 Я использовал это,

func mapDict(dict:[String:Any])->[String:String]{
    var updatedDict:[String:String] = [:]
    for key in dict.keys{
        if let value = dict[key]{
            updatedDict[key] = String(describing: value)
        }
    }

   return updatedDict
}

Использование:

let dict:[String:Any] = ["MyKey":1]
let mappedDict:[String:String] = mapDict(dict: dict)

Ссылка