Какой самый чистый способ применения 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)
Ссылка