Eu tenho uma série de Person
objetos de:
class Person {
let name:String
let position:Int
}
e a matriz é:
let myArray = [p1,p1,p3]
Quero mapear myArray
para ser um Dicionário da [position:name]
solução clássica:
var myDictionary = [Int:String]()
for person in myArray {
myDictionary[person.position] = person.name
}
existe alguma maneira elegante do Swift de fazer isso com a abordagem funcional map
, flatMap
... ou outro estilo moderno do Swift
ios
arrays
swift
dictionary
iOSGeek
fonte
fonte
[position:name]
ou[position:[name]]
? Se você tiver duas pessoas na mesma posição, seu dicionário manterá apenas a última encontrada em seu loop ... Tenho uma pergunta semelhante para a qual estou tentando encontrar uma solução, mas quero que o resultado seja como[1: [p1, p3], 2: [p2]]
Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Respostas:
Ok
map
não é um bom exemplo disso, porque é o mesmo que loop, você pode usar emreduce
vez disso, levou cada um de seus objetos para combinar e transformar em um único valor: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"]
No Swift 4 ou superior, use a resposta abaixo para uma sintaxe mais clara.
fonte
Como
Swift 4
você pode fazer a abordagem de @ Tj3n de forma mais limpa e eficiente usando ainto
versão dereduce
It, livra-se do dicionário temporário e do valor de retorno, tornando-a mais rápida e fácil de ler.Exemplo de configuração de código:
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
o parâmetro é passado para um dicionário vazio do tipo de resultado:let myDict = myArray.reduce(into: [Int: String]()) { $0[$1.position] = $1.name }
Retorna diretamente um dicionário do tipo passado em
into
:print(myDict) // [2: "c", 0: "h", 4: "b"]
fonte
$0
representa: é oinout
dicionário que estamos "devolvendo" - embora não retornando realmente, pois modificá-lo equivale a retornar devido a suainout
-ness.Desde o Swift 4, você pode fazer isso facilmente. Existem dois novos inicializadores que constroem um dicionário a partir de uma sequência de tuplas (pares de chave e valor). Se as chaves forem exclusivas, você pode fazer o seguinte:
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"]
Isso falhará com um erro de tempo de execução se alguma chave for duplicada. Nesse caso, você pode usar esta versão:
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"]
Isso se comporta como seu loop for. Se você não quiser "sobrescrever" valores e se ater ao primeiro mapeamento, pode usar isto:
Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }
=>
[1: "Franz", 2: "Heinz"]
O Swift 4.2 adiciona um terceiro inicializador que agrupa os elementos da sequência em um dicionário. As chaves do dicionário são derivadas de um encerramento. Elementos com a mesma chave são colocados em uma matriz na mesma ordem que na sequência. Isso permite que você obtenha resultados semelhantes aos acima. Por exemplo:
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)]
fonte
[1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]
. Não é o resultado esperado, mas pode ser mapeado para um dicionário simples, se desejar.Você pode escrever um inicializador personalizado para o
Dictionary
tipo, por exemplo, a partir de tuplas:extension Dictionary { public init(keyValuePairs: [(Key, Value)]) { self.init() for pair in keyValuePairs { self[pair.0] = pair.1 } } }
e, em seguida, use
map
para sua matriz dePerson
:var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})
fonte
Que tal uma solução baseada em 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 } } }
É assim que você vai usá-lo:
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"]
fonte
Você pode usar uma função de redução. Primeiro criei um inicializador designado para a classe Person
class Person { var name:String var position:Int init(_ n: String,_ p: Int) { name = n position = p } }
Mais tarde, inicializei uma matriz de valores
let myArray = [Person("Bill",1), Person("Steve", 2), Person("Woz", 3)]
E, finalmente, a variável de dicionário tem o resultado:
let dictionary = myArray.reduce([Int: Person]()){ (total, person) in var totalMutable = total totalMutable.updateValue(person, forKey: total.count) return totalMutable }
fonte
Isso é o que tenho usado
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}
Seria bom se houvesse uma maneira de combinar as 2 últimas linhas de forma que
peopleByPosition
pudesse ser alet
.Poderíamos fazer uma extensão para Array que faça isso!
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 } }
Então podemos apenas fazer
let peopleByPosition = persons.mapToDict(by: {$0.position})
fonte
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 } }
fonte
Talvez algo assim?
myArray.forEach({ myDictionary[$0.position] = $0.name })
fonte