Qual é a maneira mais limpa de aplicar map () a um dictionary no Swift?

Gostaria de mapear uma function em todas as chaves no dictionary. Eu esperava que algo como o seguinte funcionasse, mas o filtro não pode ser aplicado diretamente ao dictionary. Qual é a maneira mais limpa de conseguir isso?

Neste exemplo, estou tentando incrementar cada valor em 1. No entanto, isso é incidental para o exemplo – o objective principal é descobrir como aplicar map () a um dictionary.

var d = ["foo" : 1, "bar" : 2] d.map() { $0.1 += 1 } 

Swift 4

Boas notícias! O Swift 4 inclui um mapValues(_:) que constrói uma cópia de um dictionary com as mesmas chaves, mas valores diferentes. Ele também inclui uma sobrecarga de filter(_:) que retorna um Dictionary e init(uniqueKeysWithValues:) e init(_:uniquingKeysWith:) para criar um Dictionary partir de uma sequência arbitrária de tuplas. Isso significa que, se você quiser alterar as chaves e os valores, pode dizer algo como:

 let newDict = Dictionary(uniqueKeysWithValues: oldDict.map { key, value in (key.uppercased(), value.lowercased()) }) 

Há também novas APIs para mesclar dictionarys juntos, substituindo um valor padrão por elementos ausentes, agrupando valores (convertendo uma coleção em um dictionary de matrizes, codificado pelo resultado do mapeamento da coleção sobre alguma function) e mais.

Durante a discussão da proposta, SE-0165 , que introduziu esses resources, eu apresentei esta resposta do Stack Overflow várias vezes, e acho que o grande número de votos positivos ajudou a demonstrar a demanda. Então, obrigado pela sua ajuda para tornar o Swift melhor!

Swift 3

map é um método de extensão em Sequence and Collection . Está definido assim:

 func map(_ transform: (Iterator.Element) throws -> T) rethrows -> [T] 

Como o Dictionary está de acordo com Collection , e o seu Element typealias é uma tupla (key, value), isso significa que você deve ser capaz de fazer algo assim:

 result = dict.map { (key, value) in (key, value.uppercaseString) } 

No entanto, isso não será realmente atribuído a uma variável do tipo Dictionary . O método map é definido para sempre retornar um array (o [T] ), mesmo para outros tipos, como dictionarys. Se você escrever um construtor que transformará uma matriz de duas tuplas em um Dictionary e tudo estará certo com o 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) }) 

Você pode até querer escrever uma versão específica do dictionary do map apenas para evitar explicitamente chamar o construtor. Aqui incluí também uma implementação de filter :

 extension Dictionary { func mapPairs(_ transform: (Element) throws -> (OutKey, OutValue)) rethrows -> [OutKey: OutValue] { return Dictionary(try map(transform)) } func filterPairs(_ includeElement: (Element) throws -> Bool) rethrows -> [Key: Value] { return Dictionary(try filter(includeElement)) } } 

Eu usei nomes diferentes porque senão apenas os valores de retorno distinguiriam os dois methods, o que pode tornar o código muito ambíguo.

Use assim:

 result = dict.mapPairs { (key, value) in (key, value.uppercaseString) } 

Incidentalmente, para muitos propósitos, você pode querer mapear apenas os valores:

 extension Dictionary { func map(_ transform: (Value) throws -> OutValue) rethrows -> [Key: OutValue] { return Dictionary(try map { (k, v) in (k, try transform(v)) }) } } 

A razão é que mapPairs podem perder pares se dois forem mapeados para a mesma chave. map(Value -> OutValue) , por outro lado, sempre preserva as chaves, então isso não é um perigo.

Enquanto a maioria das respostas aqui se concentra em como mapear o dictionary inteiro (chaves e valores), a questão realmente queria apenas mapear os valores. Essa é uma distinção importante, pois os valores de mapeamento permitem garantir o mesmo número de inputs, enquanto o mapeamento de chave e valor pode resultar em chaves duplicadas.

Aqui está uma extensão, mapValues , que permite mapear apenas os valores. Note que também estende o dictionary com um init partir de uma sequência de pares de chave / valor, o que é um pouco mais geral do que inicializá-lo a partir de um array:

 extension Dictionary { init (_ seq: S) { self.init() for (k,v) in seq { self[k] = v } } func mapValues(transform: Value->T) -> Dictionary { return Dictionary(zip(self.keys, self.values.map(transform))) } } 

Com o Swift 4, você pode usar um dos cinco snippets a seguir para resolver seu problema.


# 1. Usando o 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. Usando o método de map Dictionary e o init(uniqueKeysWithValues:) inicializador

 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 Dictionary reduce(_:_:) método

 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. Usando o Dictionary subscript(_:default:) subscrito

 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. Usando o Dictionary subscript(_:) subscrito

 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] 

A maneira mais limpa é simplesmente adicionar o map ao Dicionário:

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

Verificando se 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) 

Saída:

 [c: 71, a: 51, b: 61] [c: 142, a: 51, b: 122] 

Você também pode usar reduce vez de mapear. reduce é capaz de fazer qualquer coisa map pode fazer e muito mais!

 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] 

Seria bastante fácil include isso em uma extensão, mas mesmo sem a extensão, você pode fazer o que quiser com uma chamada de function.

Acontece que você pode fazer isso. O que você precisa fazer é criar uma matriz a partir do MapCollectionView, KeyType> retornado do método de keys dictionarys. ( Info here ) Você pode mapear essa matriz e passar os valores atualizados de volta ao dictionary.

 var dictionary = ["foo" : 1, "bar" : 2] Array(dictionary.keys).map() { dictionary.updateValue(dictionary[$0]! + 1, forKey: $0) } dictionary 

De acordo com o Swift Standard Library Reference, o mapa é uma function de matrizes. Não para dictionarys.

Mas você poderia iterar seu dictionary para modificar as chaves:

 var d = ["foo" : 1, "bar" : 2] for (name, key) in d { d[name] = d[name]! + 1 } 

Eu estava procurando uma maneira de mapear um dictionary diretamente em uma matriz digitada com objects personalizados. Encontrou a solução nesta extensão:

 extension Dictionary { func mapKeys (transform: Key -> U) -> Array { var results: Array = [] for k in self.keys { results.append(transform(k)) } return results } func mapValues (transform: Value -> U) -> Array { var results: Array = [] for v in self.values { results.append(transform(v)) } return results } func map (transform: Value -> U) -> Array { return self.mapValues(transform) } func map (transform: (Key, Value) -> U) -> Array { var results: Array = [] for k in self.keys { results.append(transform(k as Key, self[ k ]! as Value)) } return results } func map (transform: (Key, Value) -> (K, V)) -> Dictionary { var results: Dictionary = [:] for k in self.keys { if let value = self[ k ] { let (u, w) = transform(k, value) results.updateValue(w, forKey: u) } } return results } } 

Usando como segue:

 self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in return VDLFilterValue(name: key, amount: value) }) 

Swift 3

Eu tento um jeito fácil no Swift 3.

Eu quero mapear [String: String?] Para [String: String] , eu uso forEach em vez de mapa ou mapa plano.

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

Eu assumo que o problema é manter as chaves do nosso dictionary original e mapear todos os seus valores de alguma forma.

Vamos generalizar e dizer que podemos querer mapear todos os valores para outro tipo .

Então, o que nós gostaríamos de começar é um dictionary e um encerramento (uma function) e acabar com um novo dictionary. O problema é, claro, que a digitação rígida de Swift atrapalha. Um encerramento precisa especificar o tipo de input e o tipo de saída, e, portanto, não podemos fazer um método que tenha um fechamento geral – exceto no contexto de um genérico. Assim, precisamos de uma function genérica.

Outras soluções se concentraram em fazer isso dentro do mundo genérico da própria estrutura genérica do Dicionário, mas acho mais fácil pensar em termos de uma function de nível superior. Por exemplo, podemos escrever isso, onde K é o tipo de chave, V1 é o tipo de valor do dictionary inicial e V2 é o tipo de valor do dictionary final:

 func mapValues(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 } 

Aqui está um exemplo simples de chamá-lo, apenas para provar que o genérico realmente se resolve em tempo de compilation:

 let d : [String:Int] = ["one":1, "two":2] let result = mapValues(d) { (i : Int) -> String in String(i) } 

Começamos com um dictionary de [String:Int] e terminamos com um dictionary de [String:String] transformando os valores do primeiro dictionary por meio de um encerramento.

(EDIT: Eu vejo agora que isso é efetivamente o mesmo que a solução AirspeedVelocity, exceto que eu não adicionei a elegância extra de um inicializador de dictionary que faz um dictionary fora de uma seqüência de zip.)

Swift 3

Uso:

 let bob = ["a": "AAA", "b": "BBB", "c": "CCC"] bob.mapDictionary { ($1, $0) } // ["BBB": "b", "CCC": "c", "AAA": "a"] 

Extensão:

 extension Dictionary { func mapDictionary(transform: (Key, Value) -> (Key, Value)?) -> Dictionary { 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 } } 

Outra abordagem é map para um dictionary e reduce , onde as funções keyTransform e valueTransform são funções.

 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 } 

Eu só estou tentando mapear os valores (potencialmente mudando o tipo deles), inclua esta extensão:

 extension Dictionary { func valuesMapped(_ transform: (Value) -> T) -> [Key: T] { var newDict = [Key: T]() for (key, value) in self { newDict[key] = transform(value) } return newDict } } 

Dado que você tem este dictionary:

 let intsDict = ["One": 1, "Two": 2, "Three": 3] 

A transformação de valor de linha única se parece com isso:

 let stringsDict = intsDict.valuesMapped { String($0 * 2) } // => ["One": "2", "Three": "6", "Two": "4"] 

A transformação de valor de várias linhas se parece com isso:

 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 eu usei isso,

 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