web-dev-qa-db-ja.com

オブジェクトの配列をSwiftの辞書にマップします

Personのオブジェクトの配列があります。

class Person {
   let name:String
   let position:Int
}

配列は次のとおりです。

let myArray = [p1,p1,p3]

myArray[position:name]の辞書にマッピングしたい古典的な解決策は次のとおりです。

var myDictionary =  [Int:String]()

for person in myArray {
    myDictionary[person.position] = person.name
}

機能的なアプローチmapflatMap...またはその他の現代のSwiftスタイルでそれを行うためのSwiftによるエレガントな方法はありますか

54
iOSGeek

わかりましたmapはこの良い例ではありません。ループとまったく同じで、代わりにreduceを使用でき、各オブジェクトを結合して単一の値に変えることができます。

let myDictionary = myArray.reduce([Int: String]()) { (dict, person) -> [Int: String] in
    var dict = dict
    dict[person.position] = person.name
    return dict
}

//[2: "b", 3: "c", 1: "a"]
81
Tj3n

Swift 4では、intoバージョンのreduceを使用して、@ Tj3nのアプローチをよりクリーンかつ効率的に行うことができます。これにより、一時辞書と戻り値が除去されるため、読みやすくなります。

struct Person { 
    let name: String
    let position: Int
}
let myArray = [Person(name:"h", position: 0), Person(name:"b", position:4), Person(name:"c", position:2)]
let myDict = myArray.reduce(into: [Int: String]()) {
    $0[$1.position] = $1.name
}
print(myDict)

注:Swift 3では機能しません。

82
possen

Swift 4なので、これは非常に簡単に行えます。タプルのシーケンス(キーと値のペア)から辞書を作成する twonew 初期化子があります。キーが一意であることが保証されている場合、次のことができます。

let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 3)]

Dictionary(uniqueKeysWithValues: persons.map { ($0.position, $0.name) })

=> [1: "Franz", 2: "Heinz", 3: "Hans"]

キーが重複している場合、これは実行時エラーで失敗します。その場合、このバージョンを使用できます。

let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 1)]

Dictionary(persons.map { ($0.position, $0.name) }) { _, last in last }

=> [1: "Hans", 2: "Heinz"]

これはforループとして動作します。値を「上書き」して最初のマッピングに固執したくない場合は、これを使用できます。

Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }

=> [1: "Franz", 2: "Heinz"]

Swift 4.2は、シーケンス要素を辞書にグループ化する third 初期化子を追加します。辞書キーはクロージャによって派生します。同じキーを持つ要素は、シーケンスと同じ順序で配列に入れられます。これにより、上記と同様の結果を得ることができます。例えば:

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.last! }

=> [1: Person(name: "Hans", position: 1), 2: Person(name: "Heinz", position: 2)]

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.first! }

=> [1: Person(name: "Franz", position: 1), 2: Person(name: "Heinz", position: 2)]

63
Mackie Messer

タプルからなど、Dictionary型のカスタム初期化子を記述できます。

extension Dictionary {
    public init(keyValuePairs: [(Key, Value)]) {
        self.init()
        for pair in keyValuePairs {
            self[pair.0] = pair.1
        }
    }
}

mapの配列にPersonを使用します。

var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})
6
Shadow Of

リデュース機能を使用できます。まず、Personクラスの指定された初期化子を作成しました

class Person {
  var name:String
  var position:Int

  init(_ n: String,_ p: Int) {
    name = n
    position = p
  }
}

後で、値の配列を初期化しました

let myArray = [Person("Bill",1), 
               Person("Steve", 2), 
               Person("Woz", 3)]

最後に、辞書変数の結果は次のとおりです。

let dictionary = myArray.reduce([Int: Person]()){
  (total, person) in
  var totalMutable = total
  totalMutable.updateValue(person, forKey: total.count)
  return totalMutable
}
2
David

たぶんこんな感じ?

myArray.forEach({ myDictionary[$0.position] = $0.name })
2
Varun Goyal
extension Array {
    func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
        var map = [T: Element]()
        self.forEach{ map[block($0)] = $0 }
        return map
    }
}
1
prashant

これは私が使用しているものです

struct Person {
    let name:String
    let position:Int
}
let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 3)]

var peopleByPosition = [Int: Person]()
persons.forEach{peopleByPosition[$0.position] = $0}

peopleByPositionletになるように最後の2行を結合する方法があったらいいでしょう。

それを行うArrayの拡張を作成できます!

extension Array {
    func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
        var map = [T: Element]()
        self.forEach{ map[block($0)] = $0 }
        return map
    }
}

その後、私たちはちょうどできる

let peopleByPosition = persons.mapToDict(by: {$0.position})
1
datinc

KeyPathベースのソリューションはどうですか?

extension Array {

    func dictionary<Key, Value>(withKey key: KeyPath<Element, Key>, value: KeyPath<Element, Value>) -> [Key: Value] {
        return reduce(into: [:]) { dictionary, element in
            let key = element[keyPath: key]
            let value = element[keyPath: value]
            dictionary[key] = value
        }
    }
}

これはあなたがそれを使用する方法です:

struct HTTPHeader {

    let field: String, value: String
}

let headers = [
    HTTPHeader(field: "Accept", value: "application/json"),
    HTTPHeader(field: "User-Agent", value: "Safari"),
]

let allHTTPHeaderFields = headers.dictionary(withKey: \.field, value: \.value)

// allHTTPHeaderFields == ["Accept": "application/json", "User-Agent": "Safari"]
0
lucamegh