Saya memiliki array Person
objek:
class Person {
let name:String
let position:Int
}
dan arraynya adalah:
let myArray = [p1,p1,p3]
Saya ingin memetakan myArray
menjadi kamus [position:name]
solusi klasik adalah:
var myDictionary = [Int:String]()
for person in myArray {
myDictionary[person.position] = person.name
}
adakah cara elegan Swift untuk melakukannya dengan pendekatan fungsional map
, flatMap
... atau gaya Swift modern lainnya
ios
arrays
swift
dictionary
iOSGeek
sumber
sumber
[position:name]
atau[position:[name]]
? Jika Anda memiliki dua orang di posisi yang sama, kamus Anda hanya akan menyimpan orang terakhir yang Anda temui dalam lingkaran Anda .... Saya memiliki pertanyaan serupa yang saya coba cari solusinya, tetapi saya ingin hasilnya seperti[1: [p1, p3], 2: [p2]]
Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Jawaban:
Oke
map
bukan contoh yang baik untuk ini, karena ini sama dengan perulangan, Anda dapat menggunakanreduce
sebagai gantinya, itu mengambil setiap objek Anda untuk digabungkan dan berubah menjadi nilai tunggal: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"]
Di Swift 4 atau lebih tinggi, gunakan jawaban di bawah ini untuk sintaks yang lebih jelas.
sumber
Karena
Swift 4
Anda dapat melakukan pendekatan @ Tj3n dengan lebih bersih dan efisien menggunakaninto
versireduce
Ini menyingkirkan kamus sementara dan nilai kembalian sehingga lebih cepat dan lebih mudah untuk dibaca.Contoh kode setup:
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)]
Into
parameter dilewatkan kamus kosong dari jenis hasil:let myDict = myArray.reduce(into: [Int: String]()) { $0[$1.position] = $1.name }
Mengembalikan secara langsung kamus dari tipe yang diteruskan
into
:print(myDict) // [2: "c", 0: "h", 4: "b"]
sumber
$0
merepresentasikan:inout
kamus itulah yang kita "kembalikan" - meskipun tidak benar-benar kembali, karena memodifikasinya sama dengan mengembalikan karena ituinout
-ness.Sejak Swift 4 Anda dapat melakukan ini dengan sangat mudah. Ada dua penginisialisasi baru yang membangun kamus dari urutan tupel (pasangan kunci dan nilai). Jika kunci dijamin unik, Anda dapat melakukan hal berikut:
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"]
Ini akan gagal dengan error runtime jika ada kunci yang diduplikasi. Dalam hal ini Anda dapat menggunakan versi ini:
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"]
Ini berperilaku sebagai loop for Anda. Jika Anda tidak ingin "menimpa" nilai dan tetap menggunakan pemetaan pertama, Anda dapat menggunakan ini:
Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }
=>
[1: "Franz", 2: "Heinz"]
Swift 4.2 menambahkan penginisialisasi ketiga yang mengelompokkan elemen urutan ke dalam kamus. Kunci kamus diturunkan dengan penutupan. Elemen dengan kunci yang sama dimasukkan ke dalam array dengan urutan yang sama seperti pada urutannya. Ini memungkinkan Anda untuk mencapai hasil yang serupa seperti di atas. Sebagai contoh:
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)]
sumber
[1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]
. Bukan hasil yang diharapkan, tetapi dapat dipetakan lebih jauh ke kamus datar jika Anda mau.Anda dapat menulis penginisialisasi khusus untuk
Dictionary
jenis, misalnya dari tupel:extension Dictionary { public init(keyValuePairs: [(Key, Value)]) { self.init() for pair in keyValuePairs { self[pair.0] = pair.1 } } }
dan kemudian gunakan
map
untuk larik Anda dariPerson
:var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})
sumber
Bagaimana dengan solusi berbasis 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 } } }
Beginilah cara Anda menggunakannya:
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"]
sumber
Anda dapat menggunakan fungsi pengurangan. Pertama saya telah membuat penginisialisasi khusus untuk kelas Person
class Person { var name:String var position:Int init(_ n: String,_ p: Int) { name = n position = p } }
Kemudian, saya telah menginisialisasi Array of values
let myArray = [Person("Bill",1), Person("Steve", 2), Person("Woz", 3)]
Dan terakhir, variabel kamus memiliki hasil:
let dictionary = myArray.reduce([Int: Person]()){ (total, person) in var totalMutable = total totalMutable.updateValue(person, forKey: total.count) return totalMutable }
sumber
Inilah yang selama ini saya gunakan
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}
Alangkah baiknya jika ada cara untuk menggabungkan 2 baris terakhir sehingga
peopleByPosition
bisa menjadilet
.Kita bisa membuat ekstensi ke Array yang melakukan itu!
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 } }
Maka kita bisa melakukannya
let peopleByPosition = persons.mapToDict(by: {$0.position})
sumber
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 } }
sumber
Mungkin sesuatu seperti ini?
myArray.forEach({ myDictionary[$0.position] = $0.name })
sumber