Was ist die sauberste Methode, um map () auf ein Wörterbuch in Swift anzuwenden?

154

Ich möchte eine Funktion auf alle Tasten im Wörterbuch abbilden. Ich hatte gehofft, dass so etwas wie das Folgende funktionieren würde, aber der Filter kann nicht direkt auf das Wörterbuch angewendet werden. Was ist der sauberste Weg, um dies zu erreichen?

In diesem Beispiel versuche ich, jeden Wert um 1 zu erhöhen. Dies ist jedoch für das Beispiel zufällig. Der Hauptzweck besteht darin, herauszufinden, wie map () auf ein Wörterbuch angewendet wird.

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

d.map() {
    $0.1 += 1
}
Maria Zverina
quelle
1
Das Wörterbuch verfügt nicht über eine Kartenfunktion, daher ist nicht klar, was Sie tun möchten.
David Berry
7
Ja - ich weiß, dass das Wörterbuch keine Kartenfunktion hat. Die Frage könnte umformuliert werden, wie dies mit Abschlüssen erreicht werden kann, ohne dass das gesamte Wörterbuch durchlaufen werden muss.
Maria Zverina
2
Sie müssen immer noch das gesamte Wörterbuch durchlaufen, da dies der mapFall ist. Was Sie verlangen, ist eine Möglichkeit, die Iteration hinter einer Erweiterung / Schließung zu verbergen, damit Sie sie nicht jedes Mal betrachten müssen, wenn Sie sie verwenden.
Joseph Mark
1
Für Swift 4 siehe meine Antwort , die bis zu 5 verschiedene Möglichkeiten zur Lösung Ihres Problems zeigt.
Imanou Petit

Antworten:

281

Swift 4+

Gute Nachrichten! Swift 4 enthält eine mapValues(_:)Methode, die eine Kopie eines Wörterbuchs mit denselben Schlüsseln, aber unterschiedlichen Werten erstellt. Es enthält auch eine filter(_:)Überlast , die eine Rückkehr Dictionaryund init(uniqueKeysWithValues:)und init(_:uniquingKeysWith:)Initialisierungen a erzeugen Dictionaryaus einer beliebigen Sequenz von Tupeln. Das heißt, wenn Sie sowohl die Schlüssel als auch die Werte ändern möchten, können Sie Folgendes sagen:

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

Es gibt auch neue APIs zum Zusammenführen von Wörterbüchern, zum Ersetzen fehlender Elemente durch einen Standardwert, zum Gruppieren von Werten (Konvertieren einer Sammlung in ein Wörterbuch von Arrays, die durch das Ergebnis der Zuordnung der Sammlung zu einer bestimmten Funktion gekennzeichnet sind) und vieles mehr.

Während der Diskussion des Vorschlags SE-0165 , der diese Funktionen einführte, habe ich diese Antwort zum Stapelüberlauf mehrmals angesprochen, und ich denke, die schiere Anzahl von Upvotes hat dazu beigetragen, die Nachfrage zu demonstrieren. Vielen Dank für Ihre Hilfe, um Swift besser zu machen!

Brent Royal-Gordon
quelle
6
Es ist sicherlich unvollkommen, aber wir dürfen nicht zulassen, dass das Vollkommene der Feind des Guten ist. Ein map, der zu widersprüchlichen Schlüsseln führen kann, ist mapin vielen Situationen immer noch nützlich . (Andererseits könnte man argumentieren, dass das Zuordnen nur der Werte eine natürliche Interpretation von mapWörterbüchern ist - sowohl das Beispiel des Originalplakats als auch mein Beispiel ändern nur die Werte, und konzeptionell mapändert ein Array in einem Array nur die Werte, nicht die Indizes.)
Brent Royal-Gordon
2
Ihr letzter Codeblock scheint zu fehlen try:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim
9
Aktualisiert für Swift 2.1? GettingDictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
Per Eriksson
4
Mit Swift 2.2 bekomme ich Argument labels '(_:)' do not match any available overloadsbei der Implementierung diese letzte Erweiterung. Ich bin mir nicht sicher, wie ich es lösen soll: /
Erken
2
@Audioy Dieses Code-Snippet hängt vom DictionaryInitialisierer ab, der in einem der vorherigen Beispiele hinzugefügt wurde. Stellen Sie sicher, dass Sie beide einschließen.
Brent Royal-Gordon
65

Mit Swift 5 können Sie eines der fünf folgenden Snippets verwenden, um Ihr Problem zu lösen.


# 1. Mit Dictionary mapValues(_:)Methode

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. Mit Dictionary mapMethode und init(uniqueKeysWithValues:)Initialisierer

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. Verwendung Dictionary reduce(_:_:)Methode oder reduce(into:_:)Verfahren

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. Mit Dictionary subscript(_:default:)Index

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. Mit Dictionary subscript(_:)Index

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]
Imanou Petit
quelle
Bitte Beispiel, dass Schlüssel ändern keine Werte. Danke hilfreich.
Yogesh Patel
18

Während sich die meisten Antworten hier darauf konzentrieren, wie das gesamte Wörterbuch (Schlüssel und Werte) zugeordnet wird, wollte die Frage eigentlich nur die Werte zuordnen. Dies ist eine wichtige Unterscheidung, da Sie durch die Zuordnung von Werten die gleiche Anzahl von Einträgen garantieren können, während die Zuordnung von Schlüssel und Wert zu doppelten Schlüsseln führen kann.

Hier ist eine Erweiterung, mapValuesmit der Sie nur die Werte zuordnen können. Beachten Sie, dass das Wörterbuch auch um initeine Folge von Schlüssel / Wert-Paaren erweitert wird, was etwas allgemeiner ist als die Initialisierung über ein 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)))
    }

}
Fluggeschwindigkeit
quelle
..wie wird es benutzt? Ich bin neu bei Swift Mind, der hier ein Beispiel gibt?
FlowUI. SimpleUITesting.com
Als ich myDictionary.map ausprobierte, musste ich innerhalb des Verschlusses auf normale Weise eine andere Karte erstellen. Das funktioniert, aber soll es so verwendet werden?
FlowUI. SimpleUITesting.com
Gibt es irgendwo eine Garantie dafür, dass die Reihenfolge der Schlüssel und Werte, wenn sie separat aufgerufen werden, gepaart und somit für die Zip-Datei geeignet ist?
Aaron Zinman
Warum ein neues Init erstellen , wenn Sie könnten nur verwenden func mapValues<T>(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }. Ich finde es liest sich auch besser. Gibt es ein Leistungsproblem?
Marcel
12

Der sauberste Weg ist, einfach mapzum Wörterbuch hinzuzufügen :

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

Überprüfen, ob es funktioniert:

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)

Ausgabe:

[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]
Joseph Mark
quelle
1
Das Problem, das ich bei diesem Ansatz sehe, ist, dass SequenceType.mapes sich nicht um eine mutierende Funktion handelt. Ihr Beispiel könnte umgeschrieben werden, um dem bestehenden Vertrag für zu folgen map, aber das entspricht der akzeptierten Antwort.
Christopher Pickslay
7

Sie können auch reduceanstelle der Karte verwenden. reduceist in der Lage etwas zu tun , mapund mehr tun können!

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]

Es wäre ziemlich einfach, dies in eine Erweiterung zu packen, aber auch ohne die Erweiterung können Sie mit einem Funktionsaufruf das tun, was Sie wollen.

Aaron Rasmussen
quelle
10
Der Nachteil dieses Ansatzes ist, dass jedes Mal, wenn Sie einen neuen Schlüssel hinzufügen, eine brandneue Kopie des Wörterbuchs erstellt wird, sodass diese Funktion schrecklich ineffizient ist (der Optimierer könnte Sie retten).
Fluggeschwindigkeit
Das ist ein guter Punkt ... eine Sache, die ich noch nicht sehr gut verstehe, sind die Interna von Swifts Speicherverwaltung. Ich freue mich über Kommentare wie diese, die mich zum Nachdenken
Aaron Rasmussen
1
Keine Notwendigkeit für Temp Var und let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
Reduce
4

Es stellt sich heraus, dass Sie dies tun können. Sie müssen lediglich ein Array aus der MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>von den Wörterbüchern zurückgegebenen keysMethode erstellen . ( Info hier ) Sie können dieses Array dann zuordnen und die aktualisierten Werte an das Wörterbuch zurückgeben.

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

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

dictionary
Mick MacCallum
quelle
Können Sie bitte eine Erklärung hinzufügen, wie ["foo": 1, "bar": 2] in [("foo", 1), ("bar", 2)]
konvertiert wird
@ MariaZverina Ich bin mir nicht sicher, was du meinst.
Mick MacCallum
Wenn Sie die oben beschriebene Konvertierung durchführen können, kann der Filter direkt auf das resultierende Array angewendet werden
Maria Zverina
@ MariaZverina Gotcha. Ja, leider ist mir keine Möglichkeit bekannt, dies zu tun.
Mick MacCallum
3

Gemäß der Swift Standard Library Reference ist map eine Funktion von Arrays. Nicht für Wörterbücher.

Sie können Ihr Wörterbuch jedoch iterieren, um die Schlüssel zu ändern:

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

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

Ich suchte nach einer Möglichkeit, ein Wörterbuch direkt in ein typisiertes Array mit benutzerdefinierten Objekten abzubilden. Die Lösung in dieser Erweiterung gefunden:

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

Verwenden Sie es wie folgt:

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

Swift 3

Ich versuche einen einfachen Weg in Swift 3.

Ich möchte Karte [String: String] zu [String: String] , verwende ich forEach statt Karte oder flache Karte.

    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
        }
    }
Kamerad
quelle
Unerwünscht, da es ein neues Array erstellt und daran anfügt
Steve Kuo
2

Swift 5

Die Kartenfunktion des Wörterbuchs wird mit dieser Syntax geliefert.

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

Sie können einfach Abschlusswerte festlegen

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

Die Ausgabe erfolgt ::

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

Es kann diese Warnung geben Result of call to 'map' is unused

Dann einfach _ =vor Variable setzen.

Vielleicht hilft es Ihnen. Danke.

NøBi Mac
quelle
1

Ich gehe davon aus, dass das Problem darin besteht, die Schlüssel unseres ursprünglichen Wörterbuchs beizubehalten und alle seine Werte auf irgendeine Weise zuzuordnen.

Lassen Sie uns verallgemeinern und sagen, dass wir möglicherweise alle Werte einem anderen Typ zuordnen möchten .

Wir möchten also mit einem Wörterbuch und einem Abschluss (einer Funktion) beginnen und am Ende ein neues Wörterbuch erhalten. Das Problem ist natürlich, dass Swifts strenge Eingabe im Weg steht. Ein Abschluss muss angeben, welcher Typ eingeht und welcher Typ herauskommt. Daher können wir keine Methode erstellen, die einen allgemeinen Abschluss erfordert - außer im Kontext eines Generikums. Wir brauchen also eine generische Funktion.

Andere Lösungen haben sich darauf konzentriert, dies innerhalb der generischen Welt der generischen Dictionary-Struktur selbst zu tun, aber ich finde es einfacher, in einer Funktion der obersten Ebene zu denken. Zum Beispiel könnten wir dies schreiben, wobei K der Schlüsseltyp ist, V1 der Werttyp des Startwörterbuchs ist und V2 der Werttyp des Endwörterbuchs ist:

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
}

Hier ist ein einfaches Beispiel für den Aufruf, um zu beweisen, dass sich das Generikum beim Kompilieren tatsächlich von selbst auflöst:

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

Wir begannen mit einem Wörterbuch von [String:Int]und endeten mit einem Wörterbuch von, [String:String]indem wir die Werte des ersten Wörterbuchs durch einen Abschluss transformierten.

(BEARBEITEN: Ich sehe jetzt, dass dies effektiv mit der Lösung von AirspeedVelocity identisch ist, außer dass ich nicht die zusätzliche Eleganz eines Dictionary-Initialisierers hinzugefügt habe, der ein Dictionary aus einer Zip-Sequenz macht.)

matt
quelle
1

Swift 3

Verwendung:

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

Erweiterung:

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
    }
}
Dimpiax
quelle
1

Wenn Sie nur versuchen, die Werte zuzuordnen (möglicherweise ihren Typ zu ändern), fügen Sie diese Erweiterung hinzu:

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

Vorausgesetzt, Sie haben dieses Wörterbuch:

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

Die einzeilige Werttransformation sieht dann folgendermaßen aus:

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

Die mehrzeilige Werttransformation sieht dann folgendermaßen aus:

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

Ein anderer Ansatz ist mapein Wörterbuch und reduce, wo Funktionen keyTransformund valueTransformFunktionen sind.

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
}
NebulaFox
quelle
Es war eher ein Konzept als tatsächlicher Code, aber ich habe es trotzdem zu laufendem Code erweitert.
NebulaFox
0

Swift 3 Ich habe das benutzt,

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
}

Verwendung:

let dict:[String:Any] = ["MyKey":1]
let mappedDict:[String:String] = mapDict(dict: dict)

Ref

Mohammad Zaid Pathan
quelle