Qual é a maneira mais limpa de aplicar o map () a um dicionário no Swift?

Gostaria de mapear uma função em todas as teclas do dicionário. Eu esperava que algo como o seguinte funcionasse, mas o filtro não pode ser aplicado diretamente ao dicionário. Qual é a maneira mais limpa de conseguir isto?

Neste exemplo, estou a tentar aumentar cada valor em 1. No entanto, isso é incidental para o exemplo-o objetivo principal é descobrir como aplicar o mapa() a um dicionário.

var d = ["foo" : 1, "bar" : 2]

d.map() {
    $0.1 += 1
}
Author: Imanou Petit, 2014-06-09

15 answers

Swift 4+

Boas notícias! Swift 4 includes a mapValues(_:) 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!
 301
Author: Brent Royal-Gordon, 2019-07-23 06:02:10

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]
 77
Author: Imanou Petit, 2019-03-22 00:25:18

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

}
 18
Author: Airspeed Velocity, 2015-04-05 19:04:14

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]
 12
Author: Joseph Mark, 2014-06-09 11:06:48

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.

 7
Author: Aaron Rasmussen, 2015-02-13 15:22:37
Parece que consegues fazer isto. O que você tem que fazer é criar um array a partir do método 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
 4
Author: Mick MacCallum, 2014-06-09 08:56:50

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
}
 3
Author: Jens Wirth, 2014-06-09 08:43:22

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)
})
 3
Author: Antoine, 2015-01-26 14:23:53

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
        }
    }
 3
Author: Mate, 2017-01-04 15:12:51

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": ...
 2
Author: Jeehut, 2017-02-07 12:54:09

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.

Pode ser que te ajude. obrigado.
 2
Author: NøBi Mac, 2020-01-22 13:44:31

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

 1
Author: matt, 2015-05-05 14:27:26

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
    }
}
 1
Author: dimpiax, 2016-11-05 02:38:21

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
}
 0
Author: NebulaFox, 2015-12-01 15:05:50

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)

Ref

 0
Author: Mohammad Zaid Pathan, 2017-05-23 12:34:45