web-dev-qa-db-fra.com

Carte de la matrice d'objets à Dictionnaire dans Swift

J'ai un tableau d'objets Person

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

et le tableau est: 

let myArray = [p1,p1,p3]

Je veux mapper myArray pour être un dictionnaire de [position:name] la solution classique est:

var myDictionary =  [Int:String]()

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

swift a-t-il une manière élégante de procéder de la sorte avec l'approche fonctionnelle map, flatMap...

36
iOSGeek

OK map n’est pas un bon exemple de cela, car c’est comme une boucle, vous pouvez utiliser reduce à la place, il a fallu que chacun de vos objets se combine et se transforme en valeur unique:

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

Dans Swift 4, vous pouvez utiliser l'approche de @ Tj3n plus proprement et plus efficacement en utilisant la version into de reduce. Le dictionnaire temporaire et la valeur renvoyée sont supprimés, ce qui le rend plus rapide et plus facile à lire.

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)

Remarque: Ne fonctionne pas dans Swift 3.

60
possen

Depuis Swift 4, vous pouvez le faire très facilement. Il y a deux _ { nouveau initialiseurs qui construisent un dictionnaire à partir d'une séquence de n-uplets (paires clé/valeur). Si les clés sont garanties uniques, vous pouvez procéder comme suit:

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

Cela échouera avec une erreur d'exécution si une clé est dupliquée. Dans ce cas, vous pouvez utiliser cette version:

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

Cela se comporte comme votre boucle for. Si vous ne voulez pas "écraser" les valeurs et vous en tenir au premier mappage, vous pouvez utiliser ceci:

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

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

Swift 4.2 ajoute un troisième initialiseur qui regroupe les éléments de séquence dans un dictionnaire. Les clés de dictionnaire sont dérivées d'une fermeture. Les éléments avec la même clé sont placés dans un tableau dans le même ordre que dans la séquence. Cela vous permet d'obtenir les mêmes résultats que ci-dessus. Par exemple:

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

Vous pouvez écrire un initialiseur personnalisé pour le type Dictionary, par exemple à partir de tuples:

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

puis utilisez map pour votre tableau de Person:

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

Vous pouvez utiliser une fonction de réduction. J'ai d'abord créé un initialiseur désigné pour la classe Person

class Person {
  var name:String
  var position:Int

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

Plus tard, j'ai initialisé un tableau de valeurs

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

Et finalement, la variable dictionnaire a le résultat suivant: 

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

C'est ce que j'utilise

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}

Ce serait bien s'il y avait un moyen de combiner les deux dernières lignes pour que peopleByPosition puisse être un let.

Nous pourrions faire une extension à Array qui fait ça!

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
    }
}

Alors on peut juste faire

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