web-dev-qa-db-ja.com

Swift 3:Array to Dictionary?

大きな配列があり、キー(ルックアップ)でアクセスする必要があるため、辞書を作成する必要があります。そうするためにSwift 3.0に組み込み関数がありますか、それとも自分で書く必要がありますか?

最初にキー「String」を持つクラスにそれが必要になり、後で多目的(すべてのタイプのデータとキー)のテンプレートバージョンを作成できるようになります。


2019年の注意。これは、現在、単純にSwiftに組み込まれているuniqueKeysWithValuesおよび同様の呼び出しです。

33
Peter71

次のようなものを探していると思います:

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
    }
}

これにより、シーケンスが繰り返され、場合によっては副作用が生じる可能性があることに注意してください。

39
overactor

それは(Swift 4)で?)

let dict = Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })


(注-キーの重複が懸念される場合は、バリエーションを使用できます
Dictionary#keysAndValues#uniquingKeysWith
これにより、あなたはpesを扱うことができます。)

63

On Swift 4、これを達成するには、Dictionary's_grouping:by:_初期化子を使用します。

例:[〜#〜] 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]] 
_

ソース

ただし、結果の辞書「辞書」のタイプは

_[String : [A]]
_

itis notタイプ

_[String : A]
_

ご想像の通り。 (後者を実現するには_#uniqueKeysWithValues_を使用します。)

37

他の人がすでに言ったように、私たちはどちらが鍵であるかを理解する必要があります。

しかし、私はあなたの質問の私の解釈に対する解決策を提供しようとしています。

struct User {
    let id: String
    let firstName: String
    let lastName: String
}

ここでは、同じidを持つ2人のユーザーが存在できないと想定しています。

let users: [User] = ...

let dict = users.reduce([String:User]()) { (result, user) -> [String:User] in
    var result = result
    result[user.id] = user
    return result
}

dictは辞書であり、keyuser idおよびvalueuser value

idを介してユーザーにアクセスするには、次のように書くことができます。

let user = dict["123"]

アップデート#1:一般的なアプローチ

指定されたタイプElementの配列、およびkeyElementを決定するクロージャーを指定すると、次の汎用関数はタイプの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

Martin Rで述べたように、reduceは関連するクロージャーの反復ごとに新しい辞書を作成します。これにより、大量のメモリが消費される可能性があります。

createIndex関数の別のバージョンは、スペース要件がO(n)で、nはelmsの長さです。

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
}
11
Luca Angeletti

この拡張機能はすべてのシーケンス(配列を含む)で機能し、キーと値の両方を選択できます。

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)}
3
John

以下は、配列を辞書に変換します。

let firstArray = [2,3,4,5,5] 

let dict = Dictionary(firstArray.map { ($0, 1) } , uniquingKeysWith: +)
3
casillas

単純にやるだけで、

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
}
2
DawnSong

あなたの質問から理解しているように、ArrayDictionaryに変換したいと思います。

私の場合、extensionArrayを作成し、辞書のキーは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
1
Oleg Gordiichuk

Mapで設定されたパターンに従い、Swiftを削減する場合は、次のような素敵で機能的なことを行うことができます。

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)]
0
Alex Pelletier