wake-up-neo.com

Zuordnen von Objekten zu Dictionary in Swift

Ich habe ein Array von Person-Objekten: 

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

und das Array ist: 

let myArray = [p1,p1,p3]

Ich möchte myArray als Wörterbuch von [position:name] abbilden. Die klassische Lösung ist:

var myDictionary =  [Int:String]()

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

gibt es eine elegante Möglichkeit von Swift, dies mit dem funktionalen Ansatz map, flatMap... oder einem anderen modernen Swift-Stil zu tun

36
iOSGeek

Okay, map ist kein gutes Beispiel dafür, da es nur das Schleifen ist. Sie können stattdessen reduce verwenden. Jedes Objekt muss kombiniert und in einen einzelnen Wert umgewandelt werden:

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"]
66
Tj3n

In Swift 4 können Sie @ Tj3ns Ansatz mit der into-Version von reduce sauberer und effizienter ausführen. Es entfernt das temporäre Wörterbuch und den Rückgabewert, sodass es schneller und einfacher zu lesen ist.

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)

Hinweis: Funktioniert nicht in Swift 3.

60
possen

Seit Swift 4 ist dies sehr einfach möglich. Es gibt zweineue Initialisierer, die ein Wörterbuch aus einer Folge von Tupeln (Schlüsselpaaren und Wertepaaren) erstellen. Wenn die Schlüssel 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 die Werte nicht "überschreiben" möchten und beim ersten Mapping bleiben 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 eine Schließung abgeleitet. Elemente mit demselben Schlüssel werden in derselben Reihenfolge wie in der Reihenfolge in ein Array eingefügt. Dadurch 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)]

35
Mackie Messer

Sie können einen benutzerdefinierten Initialisierer für den Typ Dictionary schreiben, z. B. aus Tupeln:

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

und dann map für Ihr Array von Person:

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

Vielleicht so etwas?

myArray.forEach({ myDictionary[$0.position] = $0.name })
2
Varun Goyal

Sie können eine Reduzierungsfunktion verwenden. Zuerst habe ich einen bestimmten Initializer 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
}
1
David
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

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 benutzen:

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

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 zu kombinieren, sodass peopleByPosition eine let sein könnte.

Wir könnten eine Erweiterung für Array erstellen, die das tut!

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})
0
datinc