Ich habe eine Reihe von Person
Objekten:
class Person {
let name:String
let position:Int
}
und das Array ist:
let myArray = [p1,p1,p3]
Ich möchte myArray
ein Wörterbuch [position:name]
der klassischen Lösung zuordnen:
var myDictionary = [Int:String]()
for person in myArray {
myDictionary[person.position] = person.name
}
Gibt es eine elegante Art und Weise, mit dem funktionellen Ansatz von Swift zu tun map
, flatMap
... oder anderem modernem Stil Swift
ios
arrays
swift
dictionary
iOSGeek
quelle
quelle
[position:name]
oder[position:[name]]
? Wenn Sie zwei Personen an derselben Position haben, behält Ihr Wörterbuch nur die letzte in Ihrer Schleife gefundene Person bei. Ich habe eine ähnliche Frage, für die ich nach einer Lösung suche, aber ich möchte, dass das Ergebnis so aussieht[1: [p1, p3], 2: [p2]]
Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Antworten:
Okay,
map
ist kein gutes Beispiel dafür, da es genau wie eine Schleife ist, die Siereduce
stattdessen verwenden können. Es hat jedes Ihrer Objekte benötigt, um es zu kombinieren und in einen einzelnen Wert umzuwandeln: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"]
In Swift 4 oder höher verwenden Sie bitte die folgende Antwort für eine klarere Syntax.
quelle
Da
Swift 4
Sie den Ansatz von @ Tj3n mit derinto
Version von sauberer und effizienter ausführen können, werdenreduce
das temporäre Wörterbuch und der Rückgabewert entfernt, sodass es schneller und einfacher zu lesen ist.Beispielcode-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 wird leeres Wörterbuch des Ergebnistyps übergeben:let myDict = myArray.reduce(into: [Int: String]()) { $0[$1.position] = $1.name }
Gibt direkt ein Wörterbuch des übergebenen Typs zurück
into
:print(myDict) // [2: "c", 0: "h", 4: "b"]
quelle
$0
darstellt: Es ist dasinout
Wörterbuch, das wir "zurückgeben" - obwohl es nicht wirklich zurückkehrt, da das Modifizieren es dem Zurückgeben aufgrund seinerinout
-ness entspricht.Seit Swift 4 können Sie dies sehr einfach tun. Es gibt zwei neue Initialisierer, die ein Wörterbuch aus einer Folge von Tupeln (Schlüssel- und Wertepaare) erstellen. Wenn die Schlüssel garantiert eindeutig sind, können Sie Folgendes tun:
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"]
Dies schlägt mit einem Laufzeitfehler fehl, wenn ein Schlüssel dupliziert wird. In diesem Fall können Sie diese Version verwenden:
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"]
Dies verhält sich wie Ihre for-Schleife. Wenn Sie keine Werte "überschreiben" und sich an die erste Zuordnung halten möchten, können Sie Folgendes verwenden:
Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }
=>
[1: "Franz", 2: "Heinz"]
Swift 4.2 fügt einen dritten Initialisierer hinzu, der Sequenzelemente in einem Wörterbuch gruppiert. Wörterbuchschlüssel werden durch einen Abschluss abgeleitet. Elemente mit demselben Schlüssel werden in derselben Reihenfolge wie in der Sequenz in ein Array eingefügt. Auf diese Weise können Sie ähnliche Ergebnisse wie oben erzielen. Zum Beispiel:
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)]
quelle
[1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]
. Nicht das erwartete Ergebnis, aber es kann weiter dem flachen Wörterbuch zugeordnet werden, wenn Sie dies wünschen.Sie können einen benutzerdefinierten Initialisierer für den
Dictionary
Typ schreiben , z. B. aus Tupeln:extension Dictionary { public init(keyValuePairs: [(Key, Value)]) { self.init() for pair in keyValuePairs { self[pair.0] = pair.1 } } }
und verwenden Sie dann
map
für Ihr Array vonPerson
:var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})
quelle
Wie wäre es mit einer KeyPath-basierten Lösung?
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 } } }
So werden Sie es verwenden:
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"]
quelle
Sie können eine Reduzierungsfunktion verwenden. Zuerst habe ich einen bestimmten Initialisierer für die Personenklasse erstellt
class Person { var name:String var position:Int init(_ n: String,_ p: Int) { name = n position = p } }
Später habe ich ein Array von Werten initialisiert
let myArray = [Person("Bill",1), Person("Steve", 2), Person("Woz", 3)]
Und schließlich hat die Wörterbuchvariable das Ergebnis:
let dictionary = myArray.reduce([Int: Person]()){ (total, person) in var totalMutable = total totalMutable.updateValue(person, forKey: total.count) return totalMutable }
quelle
Das habe ich benutzt
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}
Wäre schön, wenn es eine Möglichkeit gäbe, die letzten 2 Zeilen so zu kombinieren, dass
peopleByPosition
es eine sein könntelet
.Wir könnten eine Erweiterung für Array machen, die das macht!
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 } }
Dann können wir es einfach tun
let peopleByPosition = persons.mapToDict(by: {$0.position})
quelle
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 } }
quelle
Vielleicht so etwas?
myArray.forEach({ myDictionary[$0.position] = $0.name })
quelle