Tengo una matriz de Person
objetos de:
class Person {
let name:String
let position:Int
}
y la matriz es:
let myArray = [p1,p1,p3]
Quiero mapear myArray
para ser un Diccionario de [position:name]
la solución clásica es:
var myDictionary = [Int:String]()
for person in myArray {
myDictionary[person.position] = person.name
}
¿Hay alguna forma elegante de Swift de hacer eso con el enfoque funcional map
, flatMap
... u otro estilo Swift moderno?
ios
arrays
swift
dictionary
iOSGeek
fuente
fuente
[position:name]
o[position:[name]]
? Si tiene dos personas en la misma posición, su diccionario solo mantendrá la última encontrada en su bucle ... Tengo una pregunta similar para la que estoy tratando de encontrar una solución, pero quiero que el resultado sea como[1: [p1, p3], 2: [p2]]
Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Respuestas:
Bueno,
map
no es un buen ejemplo de esto, porque es lo mismo que hacer un bucle, puede usarreduce
en su lugar, tomó cada uno de sus objetos para combinar y convertir en un valor único: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"]
En Swift 4 o superior, utilice la siguiente respuesta para obtener una sintaxis más clara.
fuente
Dado
Swift 4
que puede hacer el enfoque de @ Tj3n de manera más limpia y eficiente utilizando lainto
versión dereduce
, se elimina el diccionario temporal y el valor de retorno para que sea más rápido y fácil de leer.Configuración de código de muestra:
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
el parámetro se pasa diccionario vacío del tipo de resultado:let myDict = myArray.reduce(into: [Int: String]()) { $0[$1.position] = $1.name }
Devuelve directamente un diccionario del tipo pasado
into
:print(myDict) // [2: "c", 0: "h", 4: "b"]
fuente
$0
representa: es elinout
diccionario lo que estamos "devolviendo", aunque en realidad no regresamos, ya que modificarlo es equivalente a regresar debido a su carácterinout
.Desde Swift 4 puedes hacer esto muy fácilmente. Hay dos nuevos inicializadores que crean un diccionario a partir de una secuencia de tuplas (pares de clave y valor). Si se garantiza que las claves son únicas, puede hacer lo siguiente:
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"]
Esto fallará con un error de tiempo de ejecución si se duplica alguna clave. En ese caso, puede utilizar esta versión:
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"]
Esto se comporta como su bucle for. Si no desea "sobrescribir" los valores y ceñirse al primer mapeo, puede usar esto:
Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }
=>
[1: "Franz", 2: "Heinz"]
Swift 4.2 agrega un tercer inicializador que agrupa elementos de secuencia en un diccionario. Las claves del diccionario se derivan de un cierre. Los elementos con la misma clave se colocan en una matriz en el mismo orden que en la secuencia. Esto le permite lograr resultados similares a los anteriores. Por ejemplo:
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)]
fuente
[1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]
. No es el resultado esperado, pero puede mapearse más a un diccionario plano si lo desea.Puede escribir un inicializador personalizado para el
Dictionary
tipo, por ejemplo, de tuplas:extension Dictionary { public init(keyValuePairs: [(Key, Value)]) { self.init() for pair in keyValuePairs { self[pair.0] = pair.1 } } }
y luego úselo
map
para su matriz dePerson
:var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})
fuente
¿Qué tal una solución basada en 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 } } }
Así es como lo usará:
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"]
fuente
Puede utilizar una función de reducción. Primero he creado un inicializador designado para la clase Person
class Person { var name:String var position:Int init(_ n: String,_ p: Int) { name = n position = p } }
Más tarde, inicialicé una matriz de valores
let myArray = [Person("Bill",1), Person("Steve", 2), Person("Woz", 3)]
Y finalmente, la variable de diccionario tiene el resultado:
let dictionary = myArray.reduce([Int: Person]()){ (total, person) in var totalMutable = total totalMutable.updateValue(person, forKey: total.count) return totalMutable }
fuente
Esto es lo que he estado usando
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}
Sería bueno si hubiera una manera de combinar las últimas 2 líneas para que
peopleByPosition
pudiera ser unlet
.¡Podríamos hacer una extensión de Array que haga eso!
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 } }
Entonces podemos hacer
let peopleByPosition = persons.mapToDict(by: {$0.position})
fuente
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 } }
fuente
¿Quizás algo como esto?
myArray.forEach({ myDictionary[$0.position] = $0.name })
fuente