web-dev-qa-db-de.com

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

Ich möchte allen Tasten im Wörterbuch eine Funktion zuordnen. Ich hatte gehofft, dass 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 nur ein Nebeneffekt. Das Hauptziel besteht darin, herauszufinden, wie map () auf ein Wörterbuch angewendet wird.

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

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

Swift 4

Gute Nachrichten! Swift 4 enthält eine mapValues(_:)-Methode, die eine Kopie eines Wörterbuchs mit den gleichen Schlüsseln, aber unterschiedlichen Werten erstellt. Es enthält auch eine filter(_:)-Überladung, die eine Dictionary- und init(uniqueKeysWithValues:)- und init(_:uniquingKeysWith:)-Initialisierung zurückgibt, um eine Dictionary aus einer beliebigen Folge von Tupeln zu erstellen. Das bedeutet, 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 für das Zusammenführen von Wörterbüchern, wobei fehlende Elemente durch einen Standardwert ersetzt werden, Werte gruppiert werden (Umwandlung einer Sammlung in ein Wörterbuch von Arrays, Eingabe des Ergebnisses der Zuordnung der Sammlung über eine Funktion) und mehr.

Während der Diskussion des Vorschlags SE-0165 , mit der diese Funktionen eingeführt wurden, habe ich mehrmals auf diese Stack Overflow-Antwort hingewiesen, und ich denke, die schiere Anzahl von Upvotes hat die Nachfrage deutlich gemacht. Vielen Dank für Ihre Hilfe, um Swift besser zu machen!

Swift 3

map ist eine Erweiterungsmethode für Sequence und Collection. Es ist wie folgt definiert:

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

Da Dictionary mit Collection übereinstimmt und dessen Element-Typealias ein (Schlüssel-, Wert-) Tupel ist, müssen Sie in der Lage sein, Folgendes zu tun:

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

Dies wird jedoch nicht tatsächlich einer Variablen vom Typ Dictionary zugewiesen. Die map-Methode ist so definiert, dass immer ein Array (der [T]) zurückgegeben wird, auch für andere Typen wie Wörterbücher. Wenn Sie einen Konstruktor schreiben, der ein Array von zwei Tupeln in eine Dictionary umwandelt, werden alle mit der Welt übereinstimmen:

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

Möglicherweise möchten Sie sogar eine Dictionary-spezifische Version von map schreiben, um das explizite Aufrufen des Konstruktors zu vermeiden. Hier habe ich auch eine Implementierung von filter eingefügt:

extension Dictionary {
    func mapPairs<OutKey: Hashable, OutValue>(_ transform: (Element) throws -> (OutKey, OutValue)) rethrows -> [OutKey: OutValue] {
        return Dictionary<OutKey, OutValue>(uniqueKeysWithValues: try map(transform))
    }

    func filterPairs(_ includeElement: (Element) throws -> Bool) rethrows -> [Key: Value] {
        return Dictionary(uniqueKeysWithValues: try filter(includeElement))
    }
}

Ich habe andere Namen verwendet, da ansonsten nur die Rückgabewerte die beiden Methoden unterscheiden würden, was den Code sehr vieldeutig machen kann.

Verwenden Sie es so:

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

Übrigens möchten Sie für viele Zwecke stattdessen nur die Werte zuordnen:

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

Der Grund ist, dass mapPairs Paare verlieren kann, wenn zwei am selben Schlüssel liegen. Andererseits behält map(Value -> OutValue) immer die Tasten, so dass dies keine Gefahr darstellt.

231

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


# 1. Verwenden der DictionarymapValues(_:)-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. Verwenden Sie Dictionarymap Methode und init(uniqueKeysWithValues:) Initializer

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. Verwenden der Dictionaryreduce(_:_:)-Methode

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. Dictionarysubscript(_:default:)-Index verwenden

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. Dictionarysubscript(_:)-Index verwenden

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]
27
Imanou Petit

Während sich die meisten Antworten auf die Zuordnung des gesamten Wörterbuchs (Schlüssel und Werte) konzentrieren, wollte die Frage eigentlich nur die Werte abbilden. Dies ist eine wichtige Unterscheidung, da Sie durch die Zuordnung von Werten dieselbe 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, mapValues, mit der Sie nur die Werte zuordnen können. Beachten Sie, dass das Wörterbuch außerdem um eine init aus einer Folge von Schlüssel/Wert-Paaren erweitert wird. Dies ist etwas allgemeiner als das Initialisieren aus einem 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

Der sauberste Weg ist, einfach map zu Dictionary 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, dass 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]
11
Joseph Mark

Sie können anstelle von map auch reduce verwenden. reduce kann alles, was map kann, und noch mehr!

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 relativ einfach, dies in eine Erweiterung einzubinden, aber auch ohne die Erweiterung können Sie mit einem Funktionsaufruf tun, was Sie möchten.

7
Aaron Rasmussen

Es stellt sich heraus, dass Sie dies tun können. Sie müssen ein Array aus der MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>-Methode erstellen, die von der keys-Methode der Wörterbücher zurückgegeben wird. ( 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
4
Mick MacCallum

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 durchlaufen, um die Schlüssel zu ändern:

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

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

Ich suchte nach einer Möglichkeit, ein Wörterbuch mit benutzerdefinierten Objekten direkt in ein typisiertes Array 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)
})
3
Antoine

Swift 3

Ich versuche es in Swift 3 auf einfache Weise. 

Ich möchte [String: String?] zu [String: String] zuordnen, ich verwende forEach anstelle von Map oder Flat Map.

    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
Mate

Swift 3

Verwendungszweck:

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

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

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

Also möchten wir mit einem Wörterbuch und einem Abschluss (einer Funktion) beginnen und am Ende ein neues Wörterbuch erstellen. Das Problem ist natürlich, dass Swifts strenges Tippen in die Quere kommt. Ein Abschluss muss angeben, welcher Typ in den Typ eingeht und welcher Typ herauskommt. Daher können wir keine Methode erstellen, die einen General - Abschluss nimmt, außer im Kontext eines generischen Typs. Daher brauchen wir eine generische Funktion.

Andere Lösungen haben sich darauf konzentriert, dies innerhalb der generischen Welt des Wörterbuch-generischen struct selbst zu tun, aber ich finde es einfacher, in Form einer Top-Level-Funktion zu denken. Zum Beispiel könnten wir dies schreiben, wobei K der Schlüsseltyp ist, V1 der Wertetyp des Startwörterbuchs und V2 der Wertetyp 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 ein einfaches Beispiel für den Aufruf, nur um zu beweisen, dass sich das Generikum tatsächlich zur Kompilierzeit 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.

(EDIT: Ich sehe jetzt, dass dies tatsächlich der AirspeedVelocity-Lösung entspricht, außer dass ich nicht die zusätzliche Eleganz eines Dictionary-Initialisierers hinzugefügt habe, der ein Dictionary aus einer Zip-Sequenz macht.)

1
matt

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
}

Verwendungszweck:

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

Ref

0
Zaid Pathan

Wenn Sie nur versuchen, die Werte zuzuordnen (möglicherweise ihren Typ ä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
    }
}

Dazu haben Sie dieses Wörterbuch:

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

Einzeilige Werttransformation sieht dann so aus:

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

Mehrzeilige Werttransformation sieht dann so 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": ...
0
Dschee

Ein anderer Ansatz ist map für ein Wörterbuch und reduce, wobei die Funktionen keyTransform und valueTransform Funktionen 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
}
0
NebulaFox