Tengo una matriz de Personobjetos de:
class Person {
let name:String
let position:Int
}
y la matriz es:
let myArray = [p1,p1,p3]
Quiero mapear myArraypara 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,
mapno es un buen ejemplo de esto, porque es lo mismo que hacer un bucle, puede usarreduceen 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 4que puede hacer el enfoque de @ Tj3n de manera más limpia y eficiente utilizando laintoversió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)]Intoel 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
$0representa: es elinoutdiccionario 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
Dictionarytipo, 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
mappara 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
peopleByPositionpudiera 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