Qual è il modo più pulito di applicare map () a un dizionario in Swift?


154

Vorrei mappare una funzione su tutti i tasti del dizionario. Speravo che qualcosa del genere funzionasse, ma il filtro non può essere applicato direttamente al dizionario. Qual è il modo più pulito per raggiungere questo obiettivo?

In questo esempio, sto cercando di incrementare ogni valore di 1. Tuttavia, ciò è secondario per l'esempio: lo scopo principale è capire come applicare map () a un dizionario.

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

d.map() {
    $0.1 += 1
}

1
Il dizionario non ha una funzione mappa, quindi non è chiaro cosa stai cercando di fare.
David Berry,

7
Sì, so che il dizionario non ha la funzione di mappa. La domanda potrebbe essere riformulata in quanto è possibile ottenere ciò con le chiusure senza dover scorrere l'intero dizionario.
Maria Zverina,

2
devi ancora scorrere su tutto il dizionario poiché è quello che mapfa. quello che stai chiedendo è un modo per nascondere l'iterazione dietro un'estensione / chiusura in modo da non doverlo guardare ogni volta che lo usi.
Joseph Mark,

1
Per Swift 4, vedi la mia risposta che mostra fino a 5 modi diversi per risolvere il tuo problema.
Imanou Petit,

Risposte:


281

Swift 4+

Buone notizie! Swift 4 include un mapValues(_:)metodo che costruisce una copia di un dizionario con le stesse chiavi, ma valori diversi. Esso comprende anche un filter(_:)sovraccarico che restituisce una Dictionary, e init(uniqueKeysWithValues:)e init(_:uniquingKeysWith:)inizializzatori per creare una Dictionaryda una sequenza arbitraria di tuple. Ciò significa che, se vuoi cambiare sia le chiavi che i valori, puoi dire qualcosa del tipo:

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

Esistono anche nuove API per unire i dizionari, sostituire un valore predefinito per gli elementi mancanti, raggruppare i valori (convertire una raccolta in un dizionario di matrici, codificati dal risultato della mappatura della raccolta su alcune funzioni) e altro ancora.

Durante la discussione sulla proposta, SE-0165 , che ha introdotto queste funzionalità, ho sollevato più volte questa risposta Stack Overflow e penso che il solo numero di voti positivi abbia contribuito a dimostrare la domanda. Quindi grazie per il tuo aiuto per migliorare Swift!


6
È certamente imperfetto, ma non dobbiamo lasciare che il perfetto sia nemico del bene. Uno mapche potrebbe produrre chiavi in ​​conflitto è ancora utile mapin molte situazioni. (D'altra parte, si potrebbe sostenere che mappare solo i valori è una naturale interpretazione dei mapdizionari: sia l'esempio del poster originale che il mio modificano solo i valori, e concettualmente, mapsu un array modifica solo i valori, non gli indici.)
Brent Royal-Gordon,

2
il tuo ultimo blocco di codice sembra mancare a try:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim

9
Aggiornato per Swift 2.1? Come arrivareDictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
Per Eriksson l'

4
Con Swift 2.2 sto ottenendo Argument labels '(_:)' do not match any available overloadsdurante l'implementazione dell'ultima estensione. Non sono davvero sicuro di come risolverlo: /
Erken,

2
@Audioy Lo snippet di codice dipende dall'inizializzatore Dictionaryaggiunto in uno degli esempi precedenti. Assicurati di includerli entrambi.
Brent Royal-Gordon,

66

Con Swift 5, puoi utilizzare uno dei cinque seguenti frammenti per risolvere il tuo problema.


# 1. Usando il Dictionary mapValues(_:)metodo

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. Utilizzando il Dictionary mapmetodo e l' init(uniqueKeysWithValues:)inizializzatore

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. Utilizzando il Dictionary reduce(_:_:)metodo o il reduce(into:_:)metodo

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

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 il Dictionary subscript(_:)pedice

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]

Si prega di esempio che cambiare le chiavi non i valori. Grazie utile.
Yogesh Patel,

18

Mentre la maggior parte delle risposte qui si concentra su come mappare l'intero dizionario (chiavi e valori), la domanda in realtà voleva solo mappare i valori. Questa è una distinzione importante poiché la mappatura dei valori consente di garantire lo stesso numero di voci, mentre la mappatura di chiave e valore potrebbe comportare la duplicazione delle chiavi.

Ecco un'estensione, mapValuesche ti consente di mappare solo i valori. Nota che estende anche il dizionario con una initda una sequenza di coppie chiave / valore, che è un po 'più generale rispetto all'inizializzazione da una matrice:

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

}

..come si usa? Sono nuovo per la mente veloce, mettendo un esempio qui?
FlowUI. SimpleUITesting.com

quando ho provato myDictionary.map, all'interno della chiusura, ho dovuto fare un'altra mappa in modo regolare. Funziona, ma è così che dovrebbe essere usato?
FlowUI. SimpleUITesting.com

C'è una garanzia da qualche parte che l'ordine delle chiavi e dei valori quando chiamati separatamente sono associati e quindi adatti per zip?
Aaron Zinman,

Perché creare un nuovo init quando puoi semplicemente usarlo func mapValues<T>(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }. Trovo che legga anche meglio. C'è un problema di prestazioni?
Marcel,

12

Il modo più pulito è semplicemente aggiungere mapal dizionario:

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

Verifica che funzioni:

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)

Produzione:

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

1
Il problema che vedo con questo approccio è che SequenceType.mapnon è una funzione mutante. Il tuo esempio potrebbe essere riscritto per seguire il contratto esistente map, ma è uguale alla risposta accettata.
Christopher Pickslay,

7

Puoi anche usare al reduceposto della mappa. reduceè in grado di fare tutto ciò che mappuò fare e altro!

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]

Sarebbe abbastanza facile racchiuderlo in un'estensione, ma anche senza l'estensione ti consente di fare quello che vuoi con una chiamata di funzione.


10
Il rovescio della medaglia di questo approccio è che una nuova copia del dizionario viene creata ogni volta che aggiungi una nuova chiave, quindi questa funzione è orribilmente inefficiente (l'ottimizzatore potrebbe salvarti).
Velocità della velocità

Questo è un buon punto ... una cosa che non capisco molto bene è la parte interna della gestione della memoria di Swift. Apprezzo commenti come questo che mi fanno riflettere :)
Aaron Rasmussen,

1
Non c'è bisogno di temp var e ridurre ora è un metodo in Swift 2.0:let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
Zmey

4

Si scopre che puoi farlo. Quello che devi fare è creare un array dal metodo MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>restituito dal dizionario keys. ( Informazioni qui ) È quindi possibile mappare questo array e restituire i valori aggiornati al dizionario.

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

Array(dictionary.keys).map() {
    dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}

dictionary

Puoi aggiungere una spiegazione su come convertire ["pippo": 1, "bar": 2] in [("pippo", 1), ("bar", 2)]
Maria Zverina,

@MariaZverina Non sono sicuro di cosa intendi.
Mick MacCallum,

Se puoi eseguire la conversione sopra, il filtro può essere applicato direttamente alla matrice risultante
Maria Zverina,

@MariaZverina Gotcha. Sì, sfortunatamente non sono a conoscenza di alcun modo per farlo.
Mick MacCallum,

3

Secondo lo Swift Standard Library Reference, la mappa è una funzione degli array. Non per dizionari.

Ma potresti iterare il tuo dizionario per modificare le chiavi:

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

for (name, key) in d {
    d[name] = d[name]! + 1
}

3

Stavo cercando un modo per mappare un dizionario direttamente in una matrice digitata con oggetti personalizzati. Ho trovato la soluzione in questa estensione:

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

Usandolo come segue:

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

3

Swift 3

Provo un modo semplice in Swift 3.

Voglio mappa [String: String] a [String: String] , io uso forEach invece di mappa o mappa piatta.

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

Indispensabile in quanto crea un nuovo array e lo aggiunge
Steve Kuo,

2

Swift 5

La funzione mappa del dizionario viene fornita con questa sintassi.

dictData.map(transform: ((key: String, value: String)) throws -> T)

puoi semplicemente impostare i valori di chiusura su questo

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

L'output sarà ::

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

Potrebbe essere questo avviso Result of call to 'map' is unused

Quindi basta impostare _ = prima della variabile.

Può essere che ti aiuterà Grazie.


1

Presumo che il problema sia mantenere le chiavi del nostro dizionario originale e mappare in qualche modo tutti i suoi valori.

Cerchiamo di generalizzare e dire che potremmo voler mappare tutti i valori su un altro tipo .

Quindi quello che vorremmo iniziare è un dizionario e una chiusura (una funzione) e finire con un nuovo dizionario. Il problema è, ovviamente, che la tipizzazione rigorosa di Swift si frappone. Una chiusura deve specificare quale tipo entra e quale tipo esce, e quindi non possiamo creare un metodo che prenda un generale chiusura , tranne nel contesto di un generico. Quindi abbiamo bisogno di una funzione generica.

Altre soluzioni si sono concentrate nel fare questo nel mondo generico della stessa struttura generica del Dizionario, ma trovo più facile pensare in termini di una funzione di alto livello. Ad esempio, potremmo scrivere questo, dove K è il tipo di chiave, V1 è il tipo di valore del dizionario iniziale e V2 è il tipo di valore del dizionario finale:

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
}

Ecco un semplice esempio di come chiamarlo, solo per dimostrare che il generico si risolve da solo in fase di compilazione:

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

Abbiamo iniziato con un dizionario di [String:Int]e abbiamo finito con un dizionario di[String:String] trasformando i valori del primo dizionario attraverso una chiusura.

(EDIT: vedo ora che questo è effettivamente lo stesso della soluzione di AirspeedVelocity, tranne per il fatto che non ho aggiunto l'eleganza extra di un inizializzatore di Dizionario che rende un Dizionario fuori da una sequenza zip.)


1

Swift 3

Uso:

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

Estensione:

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

Se stai solo cercando di mappare i valori (potenzialmente cambiando il loro tipo), includi questa estensione:

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

Dato che hai questo dizionario:

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

La trasformazione del valore a riga singola si presenta così:

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

La trasformazione del valore su più righe si presenta così:

let complexStringsDict = intsDict.valuesMapped { (value: Int) -> String in
    let calculationResult = (value * 3 + 7) % 5
    return String("Complex number #\(calculationResult)")
}
// => ["One": "Complex number #0", "Three": ...

0

Un altro approccio è quello mapdi un dizionario e reduce, dove funzioni keyTransforme valueTransformsono funzioni.

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
}

Era più un concetto che un codice reale, ma ciò nonostante, l'ho esteso in esecuzione del codice.
NebulaFox,

0

Swift 3 L' ho usato,

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)

arbitro

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.