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