Qual é a maneira mais limpa de aplicar o map () a um dicionário no Swift?
var d = ["foo" : 1, "bar" : 2]
d.map() {
$0.1 += 1
}
15 answers
Swift 4+
Boas notícias! Swift 4 includes amapValues(_:)
method which constructs a copy of a dictionary with the same keys, but different values. Também inclui uma sobrecarga filter(_:)
que devolve um Dictionary
, e init(uniqueKeysWithValues:)
e init(_:uniquingKeysWith:)
inicializadores para criar um Dictionary
a partir de uma sequência arbitrária de tuplas. Isso significa que, se você quiser mudar as chaves e os valores, você pode dizer algo como:
let newDict = Dictionary(uniqueKeysWithValues:
oldDict.map { key, value in (key.uppercased(), value.lowercased()) })
Existem também novas APIs para a fusão de dicionários juntos, substituindo um valor padrão para elementos em falta, agrupando valores (convertendo uma coleção em um dicionário de arrays, marcado pelo resultado de mapear a coleção sobre alguma função), e muito mais.
Durante a discussão da proposta, SE-0165, que introduziu estas características, eu levantei esta resposta de excesso de pilha várias vezes, e eu acho que o número puro de upvotes ajudou a demonstrar a demanda. Então, obrigado por sua ajuda para tornar Swift melhor!Com o Swift 5, pode usar um dos cinco excertos seguintes para resolver o seu problema.
#1. Utilização Dictionary
mapValues(_:)
método
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. Utilização Dictionary
map
método e inicializador 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. Utilização Dictionary
reduce(_:_:)
método ou reduce(into:_:)
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]
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
let newDictionary = dictionary.reduce(into: [:]) { (result: inout [String: Int], tuple: (key: String, value: Int)) in
result[tuple.key] = tuple.value + 1
}
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
#4. Utilizaçã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. Utilizaçã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]
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]
Dictionary
reduce(_:_:)
método ou reduce(into:_:)
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]
let dictionary = ["foo": 1, "bar": 2, "baz": 5]
let newDictionary = dictionary.reduce(into: [:]) { (result: inout [String: Int], tuple: (key: String, value: Int)) in
result[tuple.key] = tuple.value + 1
}
print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
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]
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]
Embora a maioria das respostas aqui se concentrem em como mapear o dicionário inteiro (valores das teclas e), a pergunta só queria mapear os valores. Esta é uma distinção importante, uma vez que os valores de mapeamento permitem garantir o mesmo número de entradas, enquanto mapear tanto a chave como o valor podem resultar em chaves duplicadas.
Aqui está uma extensão, mapValues
, que lhe permite mapear apenas os valores. Note que também estende o dicionário com um init
a partir de uma sequência de chave / valor pares, que é um pouco mais geral do que inicializá-lo a partir de um array:
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)))
}
}
A maneira mais limpa é adicionar 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)
}
}
}
A verificar 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)
Resultado:
[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]
Também pode usar reduce
em vez do mapa. reduce
é capaz de fazer qualquer coisa map
pode fazer e 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 embrulhar isto numa extensão, mas mesmo sem a extensão permite-lhe fazer o que quiser com uma chamada de função.
MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>
retornado dos dicionários keys
. (informação aqui ) poderá então mapear esta lista e passar os valores actualizados de volta para o dicionário.
var dictionary = ["foo" : 1, "bar" : 2]
Array(dictionary.keys).map() {
dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}
dictionary
De acordo com a referência da Biblioteca Padrão Swift, o mapa é uma função das matrizes. Não para dicionários.
Mas você poderia iterar o seu dicionário para modificar as chaves:
var d = ["foo" : 1, "bar" : 2]
for (name, key) in d {
d[name] = d[name]! + 1
}
Estava à procura de uma maneira de mapear um dicionário para um Array digitado com objetos personalizados. Encontrei a solução nesta extensão:
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
}
}
Usando-a como se segue:
self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in
return VDLFilterValue(name: key, amount: value)
})
Swift 3
Eu tento uma maneira fácil no Swift 3.Quero mapear [String: String?] to [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 só estás a tentar mapear os valores (Mudando potencialmente o seu tipo), inclui esta extensão:
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 tem este dicionário:
let intsDict = ["One": 1, "Two": 2, "Three": 3]
Transformação do valor de linha única depois parece-se com isto:
let stringsDict = intsDict.valuesMapped { String($0 * 2) }
// => ["One": "2", "Three": "6", "Two": "4"]
Transformação do valor multi-linha depois parece-se com isto:
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 5
A função de mapa do dicionário vem com esta sintaxe.
dictData.map(transform: ((key: String, value: String)) throws -> T)
Pode definir valores de encerramento para este
var dictData: [String: String] = [
"key_1": "test 1",
"key_2": "test 2",
"key_3": "test 3",
"key_4": "test 4",
"key_5": "test 5",
]
dictData.map { (key, value) in
print("Key :: \(key), Value :: \(value)")
}
A saída será:
Key :: key_5, Value :: test 5
Key :: key_1, Value :: test 1
Key :: key_3, Value :: test 3
Key :: key_2, Value :: test 2
Key :: key_4, Value :: test 4
Pode ser dar este Aviso Result of call to 'map' is unused
Depois, basta definir _ =
antes da variável.
Assumo que o problema é manter as chaves do nosso dicionário 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 gostaríamos de começar é um dicionário e um fechamento (uma função) e acabar com um novo dicionário. O problema é, claro, que a escrita rigorosa da Swift atrapalha. Um fechamento precisa especificar que tipo entra e que tipo sai, e assim não podemos faça um método que tome um encerramento geral - excepto no contexto de um genérico. Assim, precisamos de uma função genérica.
Outras soluções concentraram-se em fazer isto dentro do mundo Genérico da própria estrutura genérica do dicionário, mas acho mais fácil pensar em termos de uma função de alto nível. Por exemplo, poderíamos escrever isto, onde K é o tipo chave, V1 é o tipo de valor do dicionário inicial, e V2 é o tipo de valor do dicionário 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
}
Aqui está um exemplo simples de chamá-lo, apenas para provar que o genérico realmente se resolve em tempo de compilação:
let d : [String:Int] = ["one":1, "two":2]
let result = mapValues(d) { (i : Int) -> String in String(i) }
Começamos com um dicionário de [String:Int]
e acabou com um dicionário de [String:String]
transformando os valores do primeiro dicionário através de um fechamento.
(edite: vejo agora que esta é efetivamente a mesma solução do AirspeedVelocity, exceto que eu não adicionei a elegância extra de um inicializador de dicionário que faz um dicionário a partir de uma sequência zip.)
Swift 3
Utilização:
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<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
}
}
Outra abordagem é map
para um dicionário 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
}
Swift 3 {[5] } eu usei isto,
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
}
Utilização:
let dict:[String:Any] = ["MyKey":1]
let mappedDict:[String:String] = mapDict(dict: dict)