Swift 3: Массив для словаря?
У меня большой массив, и мне нужен доступ к нему по ключу (поиск), поэтому мне нужно создать словарь. Есть ли в Swift 3.0 встроенная функция для этого или мне нужно написать ее самому?
Сначала он мне понадобится для класса с ключом "String", а позже, возможно, я смогу написать шаблонную версию для общего назначения (все типы данных и ключ).
Примечание для 2019. Теперь это просто встроенный в Swift, uniqueKeysWithValues
и подобные вызовы.
Ответы
Ответ 1
Я думаю, что вы ищете что-то вроде этого:
extension Array {
public func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key:Element] {
var dict = [Key:Element]()
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
Теперь вы можете сделать:
struct Person {
var name: String
var surname: String
var identifier: String
}
let arr = [Person(name: "John", surname: "Doe", identifier: "JOD"),
Person(name: "Jane", surname: "Doe", identifier: "JAD")]
let dict = arr.toDictionary { $0.identifier }
print(dict) // Result: ["JAD": Person(name: "Jane", surname: "Doe", identifier: "JAD"), "JOD": Person(name: "John", surname: "Doe", identifier: "JOD")]
Если вы хотите, чтобы ваш код был более общим, вы даже можете добавить это расширение на Sequence
вместо Array
:
extension Sequence {
public func toDictionary<Key: Hashable>(with selectKey: (Iterator.Element) -> Key) -> [Key:Iterator.Element] {
var dict: [Key:Iterator.Element] = [:]
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
Помните, что это приводит к повторению итерации последовательности и может иметь побочные эффекты в некоторых случаях.
Ответ 2
Это так (в Swift 4)?
let dict = Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Примечание:
Как упоминалось в комментарии, использование uniqueKeysWithValues
приведет к фатальной ошибке (Fatal error: Duplicate values for key: 'your_key':
), если у вас есть дублированные ключи.
Если вы боитесь, что это может быть вашим случаем, то вы можете использовать init(_:uniquingKeysWith:)
, например.
let pairsWithDuplicateKeys = [("a", 1), ("b", 2), ("a", 3), ("b", 4)]
let firstValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (first, _) in first })
let lastValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (_, last) in last })
print(firstValues)
//prints ["a": 1, "b": 2]
print(lastValues)
//prints ["a": 3, "b": 4]
Ответ 3
В Swift 4 этого можно достичь с помощью Dictionary grouping:by:
initializer
Например:
У вас есть класс с именем A
class A {
var name: String
init(name: String) {
self.name = name
}
// .
// .
// .
// other declations and implementions
}
Далее у вас есть массив объектов типа A
let a1 = A(name: "Joy")
let a2 = A(name: "Ben")
let a3 = A(name: "Boy")
let a4 = A(name: "Toy")
let a5 = A(name: "Tim")
let array = [a1, a2, a3, a4, a5]
Допустим, вы хотите создать словарь, сгруппировав все имена по первой букве. Вы используете Swifts Dictionary(grouping:by:)
для достижения этого
let dictionary = Dictionary(grouping: array, by: { $0.name.first! })
// this will give you a dictionary
// ["J": [a1], "B": [a2, a3], "T": [a4, a5]]
источник
Однако обратите внимание, что результирующий словарь "Dictionary" имеет тип
[String : [A]]
это не типа
[String : A]
как вы можете ожидать. (Используйте #uniqueKeysWithValues
для достижения последнего.)
Ответ 4
Как уже говорили другие, нам нужно понять, какие ключи.
Однако я пытаюсь дать решение моей интерпретации вашего вопроса.
struct User {
let id: String
let firstName: String
let lastName: String
}
Здесь я предполагаю, что 2 пользователя с тем же id
не могут существовать
let users: [User] = ...
let dict = users.reduce([String:User]()) { (result, user) -> [String:User] in
var result = result
result[user.id] = user
return result
}
Теперь dict
- словарь, где key
- user id
, а value
- user value
.
Чтобы получить доступ к пользователю через его id
, вы можете просто написать
let user = dict["123"]
Обновление №1: Общий подход
Учитывая массив заданного типа Element
и замыкание, определяющее key
of Element
, следующая общая функция будет генерировать a Dictionary
типа [Key:Element]
func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
return elms.reduce([Key:Element]()) { (dict, elm) -> [Key:Element] in
var dict = dict
dict[extractKey(elm)] = elm
return dict
}
}
Пример
let users: [User] = [
User(id: "a0", firstName: "a1", lastName: "a2"),
User(id: "b0", firstName: "b1", lastName: "b2"),
User(id: "c0", firstName: "c1", lastName: "c2")
]
let dict = createIndex(elms: users) { $0.id }
// ["b0": {id "b0", firstName "b1", lastName "b2"}, "c0": {id "c0", firstName "c1", lastName "c2"}, "a0": {id "a0", firstName "a1", lastName "a2"}]
Обновление # 2
Как отметил Мартин R, сокращение создаст новый словарь для каждой итерации связанного закрытия. Это может привести к огромному потреблению памяти.
Здесь другая версия функции createIndex
, где требование пространства - это O (n), где n - длина вяза.
func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
var dict = [Key:Element]()
for elm in elms {
dict[extractKey(elm)] = elm
}
return dict
}
Ответ 5
Следующее преобразует массив в словарь.
let firstArray = [2,3,4,5,5]
let dict = Dictionary(firstArray.map { ($0, 1) } , uniquingKeysWith: +)
Ответ 6
Это расширение работает для всех последовательностей (включая массивы) и позволяет выбрать и ключ и значение:
extension Sequence {
public func toDictionary<K: Hashable, V>(_ selector: (Iterator.Element) throws -> (K, V)?) rethrows -> [K: V] {
var dict = [K: V]()
for element in self {
if let (key, value) = try selector(element) {
dict[key] = value
}
}
return dict
}
}
Пример:
let nameLookup = persons.toDictionary{($0.name, $0)}
Ответ 7
Просто сделайте это просто,
let items = URLComponents(string: "https://im.qq.com?q=13&id=23")!.queryItems!
var dic = [String: Any?]()
items.foreach {
dic[$0.name] = $0.value
}
reduce
не очень подходит,
let dic: [String: Any?] = items.reduce([:]) { (result: [String: Any?], item: URLQueryItem) -> [String: Any?] in
var r = result
r[item.name] = item.value // will create an copy of result!!!!!!
return r
}
Ответ 8
Как я понимаю из вашего вопроса, вы хотели бы преобразовать в Array
в Dictionary
.
В моем случае я создаю extension
для Array
, а клавиши для словаря будут индексами Array
.
Пример:
var intArray = [2, 3, 5, 3, 2, 1]
extension Array where Element: Any {
var toDictionary: [Int:Element] {
var dictionary: [Int:Element] = [:]
for (index, element) in enumerate() {
dictionary[index] = element
}
return dictionary
}
}
let dic = intArray.toDictionary
Ответ 9
Если вы хотите следовать шаблону, указанному в map, и быстро уменьшить его, вы можете сделать что-то хорошее и функциональное, например:
extension Array {
func keyBy<Key: Hashable>(_ keyFor: (Element) -> Key) -> [Key: Element] {
var ret = [Key: Element]()
for item in self{
ret[keyFor(item)] = item
}
return ret
}
}
Использование:
struct Dog {
let id: Int
}
let dogs = [Dog(id: 1), Dog(id: 2), Dog(id: 3), Dog(id: 4)]
let dogsById = dogs.keyBy({ $0.id })
// [4: Dog(id: 4), 1: Dog(id: 1), 3: Dog(id: 3), 2: Dog(id: 2)]