example - ¿Cuál es la forma más limpia de aplicar map() a un diccionario en Swift?
objectmapper swift 4 (14)
Con Swift 4, puede usar uno de los cinco fragmentos siguientes para resolver su problema.
# 1. Usando el mapValues(_:)
Dictionary
mapValues(_:)
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
let newDictionary = dictionary.mapValues { value in
return value + 1
}
//let newDictionary = dictionary.mapValues { $0 + 1 } // also works
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
# 2. Uso del método de map
Dictionary
e init(uniqueKeysWithValues:)
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
let tupleArray = dictionary.map { (key: String, value: Int) in
return (key, value + 1)
}
//let tupleArray = dictionary.map { ($0, $1 + 1) } // also works
let newDictionary = Dictionary(uniqueKeysWithValues: tupleArray)
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
# 3. Usando el método Dictionary
reduce(_:_:)
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
let newDictionary = dictionary.reduce([:]) { (partialResult: [String: Int], tuple: (key: String, value: Int)) in
var result = partialResult
result[tuple.key] = tuple.value + 1
return result
}
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
# 4. Uso del subscript(_:default:)
Dictionary
subscript(_:default:)
subíndice
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
var newDictionary = [String: Int]()
for (key, value) in dictionary {
newDictionary[key, default: value] += 1
}
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
# 5. Uso del subscript(_:)
Dictionary
subscript(_:)
subíndice
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
var newDictionary = [String: Int]()
for (key, value) in dictionary {
newDictionary[key] = value + 1
}
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
Me gustaría asignar una función a todas las teclas del diccionario. Esperaba que algo como lo siguiente funcionara, pero el filtro no se puede aplicar al diccionario directamente. ¿Cuál es la forma más limpia de lograr esto?
En este ejemplo, estoy tratando de incrementar cada valor en 1. Sin embargo, esto es incidental para el ejemplo, el objetivo principal es descubrir cómo aplicar map () a un diccionario.
var d = ["foo" : 1, "bar" : 2]
d.map() {
$0.1 += 1
}
Estaba buscando una manera de mapear un diccionario directamente en una matriz tipada con objetos personalizados. Encontré la solución en esta extensión:
extension Dictionary {
func mapKeys<U> (transform: Key -> U) -> Array<U> {
var results: Array<U> = []
for k in self.keys {
results.append(transform(k))
}
return results
}
func mapValues<U> (transform: Value -> U) -> Array<U> {
var results: Array<U> = []
for v in self.values {
results.append(transform(v))
}
return results
}
func map<U> (transform: Value -> U) -> Array<U> {
return self.mapValues(transform)
}
func map<U> (transform: (Key, Value) -> U) -> Array<U> {
var results: Array<U> = []
for k in self.keys {
results.append(transform(k as Key, self[ k ]! as Value))
}
return results
}
func map<K: Hashable, V> (transform: (Key, Value) -> (K, V)) -> Dictionary<K, V> {
var results: Dictionary<K, V> = [:]
for k in self.keys {
if let value = self[ k ] {
let (u, w) = transform(k, value)
results.updateValue(w, forKey: u)
}
}
return results
}
}
Utilizándolo de la siguiente manera:
self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in
return VDLFilterValue(name: key, amount: value)
})
La forma más limpia es simplemente agregar un map
al Diccionario:
extension Dictionary {
mutating func map(transform: (key:KeyType, value:ValueType) -> (newValue:ValueType)) {
for key in self.keys {
var newValue = transform(key: key, value: self[key]!)
self.updateValue(newValue, forKey: key)
}
}
}
Comprobando que funciona:
var dic = ["a": 50, "b": 60, "c": 70]
dic.map { $0.1 + 1 }
println(dic)
dic.map { (key, value) in
if key == "a" {
return value
} else {
return value * 2
}
}
println(dic)
Salida:
[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]
Otro enfoque es map
a un diccionario y reduce
, donde las funciones keyTransform
y valueTransform
son funciones.
let dictionary = ["a": 1, "b": 2, "c": 3]
func keyTransform(key: String) -> Int {
return Int(key.unicodeScalars.first!.value)
}
func valueTransform(value: Int) -> String {
return String(value)
}
dictionary.map { (key, value) in
[keyTransform(key): valueTransform(value)]
}.reduce([Int:String]()) { memo, element in
var m = memo
for (k, v) in element {
m.updateValue(v, forKey: k)
}
return m
}
Resulta que puedes hacer esto. Lo que tiene que hacer es crear una matriz a partir de MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>
devuelto por el método de keys
diccionarios. ( Información aquí ) Puede asignar esta matriz y pasar los valores actualizados al diccionario.
var dictionary = ["foo" : 1, "bar" : 2]
Array(dictionary.keys).map() {
dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}
dictionary
Según la Referencia de la Biblioteca Estándar Swift, el mapa es una función de las matrices. No para diccionarios.
Pero podría iterar su diccionario para modificar las claves:
var d = ["foo" : 1, "bar" : 2]
for (name, key) in d {
d[name] = d[name]! + 1
}
Si bien la mayoría de las respuestas aquí se centran en cómo mapear todo el diccionario (claves y valores), la pregunta realmente solo quería asignar los valores. Esta es una distinción importante ya que la asignación de valores le permite garantizar el mismo número de entradas, mientras que el mapeo de la clave y el valor puede dar como resultado claves duplicadas.
Aquí hay una extensión, mapValues
, que le permite asignar solo los valores. Tenga en cuenta que también amplía el diccionario con un init
partir de una secuencia de pares clave / valor, que es un poco más general que inicializarlo desde una matriz:
extension Dictionary {
init<S: SequenceType where S.Generator.Element == Element>
(_ seq: S) {
self.init()
for (k,v) in seq {
self[k] = v
}
}
func mapValues<T>(transform: Value->T) -> Dictionary<Key,T> {
return Dictionary<Key,T>(zip(self.keys, self.values.map(transform)))
}
}
Supongo que el problema es mantener las claves de nuestro diccionario original y asignar todos sus valores de alguna manera.
Permítanos generalizar y decir que podemos querer mapear todos los valores a otro tipo .
Entonces, nos gustaría comenzar con un diccionario y un cierre (una función) y terminar con un nuevo diccionario. El problema es, por supuesto, que la tipificación estricta de Swift se interpone en el camino. Un cierre necesita especificar qué tipo entra y qué tipo sale, y por lo tanto no podemos hacer un método que tenga un cierre general , excepto en el contexto de un genérico. Por lo tanto, necesitamos una función genérica.
Otras soluciones se han concentrado en hacer esto en el mundo genérico de la estructura genérica del diccionario en sí, pero me resulta más fácil pensar en términos de una función de nivel superior. Por ejemplo, podríamos escribir esto, donde K es el tipo de clave, V1 es el tipo de valor del diccionario inicial, y V2 es el tipo de valor del diccionario final:
func mapValues<K,V1,V2>(d1:[K:V1], closure:(V1)->V2) -> [K:V2] {
var d2 = [K:V2]()
for (key,value) in zip(d1.keys, d1.values.map(closure)) {
d2.updateValue(value, forKey: key)
}
return d2
}
Aquí hay un ejemplo simple de llamarlo, solo para demostrar que el genérico realmente se resuelve en tiempo de compilación:
let d : [String:Int] = ["one":1, "two":2]
let result = mapValues(d) { (i : Int) -> String in String(i) }
Comenzamos con un diccionario de [String:Int]
y terminamos con un diccionario de [String:String]
transformando los valores del primer diccionario a través de un cierre.
(EDIT: ahora veo que esto es efectivamente lo mismo que la solución de AirspeedVelocity, excepto que no agregué la elegancia extra de un inicializador de diccionario que hace que un diccionario salga de una secuencia zip).
También puedes usar reduce
lugar de mapa. reduce
es capaz de hacer cualquier cosa que el map
pueda hacer ¡y más!
let oldDict = ["old1": 1, "old2":2]
let newDict = reduce(oldDict, [String:Int]()) { dict, pair in
var d = dict
d["new/(pair.1)"] = pair.1
return d
}
println(newDict) // ["new1": 1, "new2": 2]
Sería bastante fácil envolver esto en una extensión, pero incluso sin la extensión, le permite hacer lo que quiera con una llamada a función.
Yo solo estoy tratando de mapear los valores (potencialmente cambiando su tipo), incluya esta extensión:
extension Dictionary {
func valuesMapped<T>(_ transform: (Value) -> T) -> [Key: T] {
var newDict = [Key: T]()
for (key, value) in self {
newDict[key] = transform(value)
}
return newDict
}
}
Dado que tienes este diccionario:
let intsDict = ["One": 1, "Two": 2, "Three": 3]
La transformación de valor de una línea se ve así:
let stringsDict = intsDict.valuesMapped { String($0 * 2) }
// => ["One": "2", "Three": "6", "Two": "4"]
La transformación de valores de varias líneas se ve así:
let complexStringsDict = intsDict.valuesMapped { (value: Int) -> String in
let calculationResult = (value * 3 + 7) % 5
return String("Complex number #/(calculationResult)")
}
// => ["One": "Complex number #0", "Three": ...
Swift 3
Lo intento de una manera fácil en Swift 3.
Quiero asignar [String: String?] A [String: String] , uso forEach en lugar de map o flat map.
let oldDict = ["key0": "val0", "key1": nil, "key1": "val2","key2": nil]
var newDict = [String: String]()
oldDict.forEach { (source: (key: String, value: String?)) in
if let value = source.value{
newDict[source.key] = value
}
}
Swift 3
Uso:
let bob = ["a": "AAA", "b": "BBB", "c": "CCC"]
bob.mapDictionary { ($1, $0) } // ["BBB": "b", "CCC": "c", "AAA": "a"]
Extensión:
extension Dictionary {
func mapDictionary(transform: (Key, Value) -> (Key, Value)?) -> Dictionary<Key, Value> {
var dict = [Key: Value]()
for key in keys {
guard let value = self[key], let keyValue = transform(key, value) else {
continue
}
dict[keyValue.0] = keyValue.1
}
return dict
}
}
Swift 3 Utilicé esto,
func mapDict(dict:[String:Any])->[String:String]{
var updatedDict:[String:String] = [:]
for key in dict.keys{
if let value = dict[key]{
updatedDict[key] = String(describing: value)
}
}
return updatedDict
}
Uso:
let dict:[String:Any] = ["MyKey":1]
let mappedDict:[String:String] = mapDict(dict: dict)
Ref
Swift 4
¡Buenas noticias! Swift 4 incluye un mapValues(_:)
que construye una copia de un diccionario con las mismas claves, pero diferentes valores. También incluye una sobrecarga de filter(_:)
que devuelve un Dictionary
, e init(uniqueKeysWithValues:)
e init(_:uniquingKeysWith:)
para crear un Dictionary
partir de una secuencia arbitraria de tuplas. Eso significa que, si desea cambiar las claves y los valores, puede decir algo como:
let newDict = Dictionary(uniqueKeysWithValues:
oldDict.map { key, value in (key.uppercased(), value.lowercased()) })
También hay nuevas API para fusionar diccionarios juntos, sustituyendo un valor predeterminado por elementos faltantes, agrupando valores (convirtiendo una colección en un diccionario de matrices, codificado por el resultado de mapear la colección sobre alguna función) y más.
Durante la discusión de la propuesta, SE-0165 , que introdujo estas características, mencioné varias veces esta respuesta de desbordamiento de pila, y creo que la gran cantidad de votos positivos ayudó a demostrar la demanda. ¡Gracias por tu ayuda para mejorar Swift!
Swift 3
map
es un método de extensión en Sequence
and Collection
. Se define así:
func map<T>(_ transform: (Iterator.Element) throws -> T) rethrows -> [T]
Como Dictionary
ajusta a Collection
, y su Typealias Element
es una tupla (clave, valor), eso significa que debería poder hacer algo como esto:
result = dict.map { (key, value) in (key, value.uppercaseString) }
Sin embargo, eso no se asignará a una variable tipo Dictionary
. El método del map
está definido para devolver siempre una matriz (la [T]
), incluso para otros tipos, como los diccionarios. Si escribe un constructor que convertirá una matriz de dos tuplas en un Dictionary
y todo estará bien con el mundo:
extension Dictionary {
init(_ pairs: [Element]) {
self.init()
for (k, v) in pairs {
self[k] = v
}
}
}
result = Dictionary(dict.map { (key, value) in (key, value.uppercaseString) })
Incluso puede escribir una versión del map
específica del diccionario solo para evitar llamar explícitamente al constructor. Aquí también incluí una implementación de filter
:
extension Dictionary {
func mapPairs<OutKey: Hashable, OutValue>(_ transform: (Element) throws -> (OutKey, OutValue)) rethrows -> [OutKey: OutValue] {
return Dictionary<OutKey, OutValue>(try map(transform))
}
func filterPairs(_ includeElement: (Element) throws -> Bool) rethrows -> [Key: Value] {
return Dictionary(try filter(includeElement))
}
}
Usé nombres diferentes porque, de lo contrario, solo los valores de retorno distinguirían los dos métodos, lo que puede hacer que el código sea muy ambiguo.
Úselo así:
result = dict.mapPairs { (key, value) in (key, value.uppercaseString) }
Por cierto, para muchos propósitos, en su lugar, puede querer asignar solo los valores:
extension Dictionary {
func map<OutValue>(_ transform: (Value) throws -> OutValue) rethrows -> [Key: OutValue] {
return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
}
}
La razón es que mapPairs
puede perder pares si dos terminan asignados a la misma clave. map(Value -> OutValue)
, por otro lado, siempre conserva las claves, por lo que esto no es un peligro.