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
}
機能的なアプローチmap
、flatMap
...またはその他の現代のSwiftスタイルでそれを行うためのSwiftによるエレガントな方法はありますか
わかりました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"]
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では機能しません。
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)]
タプルからなど、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)})
リデュース機能を使用できます。まず、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
}
たぶんこんな感じ?
myArray.forEach({ myDictionary[$0.position] = $0.name })
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
}
}
これは私が使用しているものです
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}
peopleByPosition
がlet
になるように最後の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})
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"]