Есть ли декларативный способ преобразования Array в словарь?
Я хочу получить из этого массива строк
let entries = ["x=5", "y=7", "z=10"]
к этому
let keyValuePairs = ["x" : "5", "y" : "7", "z" : "10"]
Я попытался использовать map
, но проблема в том, что пара ключевых слов в словаре не является отдельным типом, это просто на мой взгляд, но не в словаре, поэтому я не мог реально предоставить функция преобразования, потому что нечего преобразовать. Плюс map
возвращает массив, чтобы он не прошел.
Любые идеи?
Ответы
Ответ 1
Swift 4
Как упоминается fl034, это может быть упрощено с помощью Swift 4, где версия с проверкой ошибок выглядит так:
let foo = entries
.map { $0.components(separatedBy: "=") }
.reduce(into: [String:Int64]()) { dict, pair in
if pair.count == 2, let value = Int64(pair[1]) {
dict[pair[0]] = value
}
}
Еще проще, если вам не нужны значения как Ints:
let foo = entries
.map { $0.components(separatedBy: "=") }
.reduce(into: [String:String]()) { dict, pair in
if pair.count == 2 {
dict[pair[0]] = pair[1]
}
}
Старые TL, DR
Ошибка проверки минусов, это выглядит примерно так:
let foo = entries.map({ $0.componentsSeparatedByString("=") })
.reduce([String:Int]()) { acc, comps in
var ret = acc
ret[comps[0]] = Int(comps[1])
return ret
}
Используйте карту, чтобы превратить [String]
в разделение [[String]]
, а затем постройте словарь [String:Int]
с помощью сокращения.
Или, добавив расширение к Dictionary
:
extension Dictionary {
init(elements:[(Key, Value)]) {
self.init()
for (key, value) in elements {
updateValue(value, forKey: key)
}
}
}
(Довольно полезное расширение btw, вы можете использовать его для большого количества операций с картами/фильтрами в словарях, поистине, позор, которого он не существует по умолчанию)
Это становится еще проще:
let dict = Dictionary(elements: entries
.map({ $0.componentsSeparatedByString("=") })
.map({ ($0[0], Int($0[1])!)})
)
Конечно, вы также можете комбинировать два вызова карты, но я предпочитаю разбивать отдельные преобразования.
Если вы хотите добавить некоторую проверку ошибок, вместо map
можно использовать flatMap
:
let dict2 = [String:Int](elements: entries
.map({ $0.componentsSeparatedByString("=") })
.flatMap({
if $0.count == 2, let value = Int($0[1]) {
return ($0[0], value)
} else {
return nil
}})
)
Опять же, если вы хотите, вы можете, очевидно, объединить map
в flatMap
или разбить их для простоты.
let dict2 = [String:Int](elements: entries.flatMap {
let parts = $0.componentsSeparatedByString("=")
if parts.count == 2, let value = Int(parts[1]) {
return (parts[0], value)
} else {
return nil
}}
)
Ответ 2
Один из способов сделать это - в два этапа с map
и reduce
с кортежем в качестве промежуточного значения, например:
let entries = ["x=5", "y=7", "z=10"]
let dict = entries.map { (str) -> (String, String) in
let elements = str.characters.split("=").map(String.init)
return (elements[0], elements[1])
}.reduce([String:String]()) { (var dict, kvpair) in
dict[kvpair.0] = kvpair.1
return dict
}
for key in dict.keys {
print("Value for key '\(key)' is \(dict[key]).")
}
выходы:
Value for key 'y' is Optional("7").
Value for key 'x' is Optional("5").
Value for key 'z' is Optional("10").
или с одним reduce
с тем же выходом:
let entries = ["x=5", "y=7", "z=10"]
let dict = entries.reduce([String:String]()) { (var dict, entry) in
let elements = entry.characters.split("=").map(String.init)
dict[elements[0]] = elements[1]
return dict
}
for key in dict.keys {
print("Value for key '\(key)' is \(dict[key]).")
}
Ответ 3
Хорошие ответы уже есть. Вот еще один способ с расширением типа коллекции. Вы можете преобразовать любой тип коллекции в словарь, массив или набор.
extension CollectionType {
func map2Dict<K, V>(@noescape map: ((Self.Generator.Element) -> (K, V)?)) -> [K: V] {
var d = [K: V]()
for e in self {
if let kV = map(e) {
d[kV.0] = kV.1
}
}
return d
}
func map2Array<T>(@noescape map: ((Self.Generator.Element) -> (T)?)) -> [T] {
var a = [T]()
for e in self {
if let o = map(e) {
a.append(o)
}
}
return a
}
func map2Set<T>(@noescape map: ((Self.Generator.Element) -> (T)?)) -> Set<T> {
return Set(map2Array(map))
}
}
Здесь пример использования.
let entries = ["x=5", "y=7", "z=10"]
let dict:[String: String] = entries.map2Dict({
let components = $0.componentsSeparatedByString("=")
return (components.first!, components.last!)
})
print("dict \(dict)")
Ответ 4
Мне нравится ответ @paulgriffiths, но если у вас есть крайнее отвращение к ошибкам во время выполнения, вы можете сделать еще один шаг, чтобы каждая строка в начальном массиве имела как необходимые элементы...
Важное различие в этом коде по сравнению с остальными заключается в том, что я проверяю, что на самом деле есть "=" в строке с элементами с обеих сторон. flatMap
эффективно отфильтровывает любые отказы.
extension Dictionary {
func withUpdate(key: Key, value: Value) -> Dictionary<Key, Value> {
var result = self
result[key] = value
return result
}
}
let entries = ["x=5", "y=7", "z=10"]
let keyValues = entries.flatMap { str -> (String, String)? in
let split = str.characters.split("=").map(String.init)
return split.count > 1 ? (split[0], split[1]) : nil
}
let keyValuePairs = keyValues.reduce([String: String]()) { $0.withUpdate($1.0, value: $1.1) }
Ответ 5
И для всех, кто любит перегрузку и однострочные.
public func +<K, V>(lhs: [K:V], rhs: [K:V]) -> [K:V] {
var lhs: [K:V] = lhs
for (key, value) in rhs {
lhs[key] = value
}
return lhs
}
let array = ["x=5", "y=7", "z=10"]
let dictionary = array.map({ $0.componentsSeparatedByString("=") }).reduce([:]) { $0 + [$1[0]: $1[1]] }
print(dictionary) // ["y": "7", "x": "5", "z": "10"]
Ответ 6
Если у вас есть два параллельных массива, вы можете использовать zip()
, Array()
и расширение словаря David Berry:
let numbers = [10, 9, 8]
let strings = ["one", "two", "three", "four"]
let dictionary = Dictionary(elements: Array(zip(numbers, strings)))
// == [10: "one", 9: "two", 8: "three"]
Не забудьте добавить расширение:
extension Dictionary {
init(elements:[(Key, Value)]) {
self.init()
for (key, value) in elements {
updateValue(value, forKey: key)
}
}
}
Ответ 7
Простой способ:
let array = ["key1", "key2", "key3"]
let dict = array.flatMap({["\($0)" : "value"]})
Вывод:
[(ключ: "ключ1", значение: "значение" ), (ключ: "ключ2", значение: "значение" ), (клавиша: "ключ3", значение: "значение" )]
Немного сложнее:
let array = ["key1", "key2", "key3"]
let dict = array.enumerated().flatMap({["\($0.element)" : "value\($0.offset)"]})
Выход:
[(ключ: "ключ1", значение: "значение0" ), (ключ: "ключ2", значение: "значение1" ), (клавиша: "ключ3", значение: "значение2" )]
Ответ 8
Используйте Swift 4 новый reduce(into: Result)
метод:
let keyValuePairs = entries.reduce(into: [String:String]()) { (dict, entry) in
let key = String(entry.first!)
let value = String(entry.last!)
dict[entry.first!] = entry.last!
}
Конечно, можно улучшить разбиение вашей строки.