Jaki jest najczystszy sposób stosowania funkcji map () do słownika w języku Swift?

154

Chciałbym przypisać funkcję do wszystkich klawiszy w słowniku. Miałem nadzieję, że coś takiego zadziała, ale nie można zastosować filtra bezpośrednio do słownika. Jaki jest najczystszy sposób osiągnięcia tego?

W tym przykładzie próbuję zwiększyć każdą wartość o 1. Jednak jest to przypadkowe w tym przykładzie - głównym celem jest ustalenie, jak zastosować map () do słownika.

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

d.map() {
    $0.1 += 1
}
Maria Zverina
źródło
1
Słownik nie ma funkcji mapy, więc nie jest jasne, co próbujesz zrobić.
David Berry
7
Tak - wiem, że słownik nie ma funkcji mapy. Pytanie można przeformułować, ponieważ w jaki sposób można to osiągnąć za pomocą domknięć bez konieczności iterowania po całym słowniku.
Maria Zverina
2
nadal musisz iterować po całym słowniku, ponieważ to właśnie maprobi a . to, o co prosisz, to sposób na ukrycie iteracji za rozszerzeniem / zamknięciem, dzięki czemu nie musisz na to patrzeć za każdym razem, gdy go używasz.
Joseph Mark,
1
W przypadku Swift 4 zobacz moją odpowiedź, która pokazuje do 5 różnych sposobów rozwiązania problemu.
Imanou Petit

Odpowiedzi:

281

Swift 4+

Dobre wieści! Swift 4 zawiera mapValues(_:)metodę, która tworzy kopię słownika z tymi samymi kluczami, ale różnymi wartościami. Zawiera również plikfilter(_:) przeciążenia, która zwraca Dictionaryi init(uniqueKeysWithValues:)i init(_:uniquingKeysWith:)inicjatorów, aby utworzyć Dictionaryz dowolnej sekwencji krotki. Oznacza to, że jeśli chcesz zmienić zarówno klucze, jak i wartości, możesz powiedzieć coś takiego:

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

Istnieją również nowe interfejsy API do łączenia ze sobą słowników, zastępowania wartości domyślnej brakujących elementów, grupowania wartości (konwertowanie kolekcji do słownika tablic z kluczem wynikającym z odwzorowania kolekcji na jakiejś funkcji) i nie tylko.

Podczas dyskusji nad propozycją SE-0165 , która wprowadziła te funkcje, kilkakrotnie przywołałem tę odpowiedź Stack Overflow i myślę, że sama liczba głosów za pomogła zademonstrować zapotrzebowanie. Więc dziękuję za pomoc w ulepszaniu Swift!

Brent Royal-Gordon
źródło
6
Jest to z pewnością niedoskonałe, ale nie możemy pozwolić, aby doskonałe było wrogiem dobra. A, mapktóry może generować sprzeczne klucze, jest nadal przydatny mapw wielu sytuacjach. (Z drugiej strony można argumentować, że mapowanie tylko wartości jest naturalną interpretacją mapsłowników - zarówno przykład oryginalnego postera, jak i mój, modyfikują tylko wartości, a koncepcyjnie maptablica modyfikuje tylko wartości, a nie indeksy.)
Brent Royal-Gordon
2
wydaje się, że brakuje twojego ostatniego bloku kodu try:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim
9
Zaktualizowano do wersji Swift 2.1? PierwszeDictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
Per Eriksson
4
Dzięki Swift 2.2 otrzymuję Argument labels '(_:)' do not match any available overloads, wdrażając to ostatnie rozszerzenie. Nie bardzo wiem, jak to rozwiązać: /
Erken
2
@Audioy Ten fragment kodu zależy od Dictionaryinicjatora dodanego w jednym z poprzednich przykładów. Upewnij się, że uwzględnisz oba z nich.
Brent Royal-Gordon
65

Dzięki Swift 5 możesz użyć jednego z pięciu poniższych fragmentów , aby rozwiązać problem.


# 1. Korzystanie z Dictionary mapValues(_:)metody

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. Korzystanie z Dictionary mapmetody i init(uniqueKeysWithValues:)inicjatora

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. Używając Dictionary reduce(_:_:)metody lub reduce(into:_:)metody

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. Korzystanie z Dictionary subscript(_:default:)indeksu dolnego

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. Korzystanie z Dictionary subscript(_:)indeksu dolnego

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
źródło
Proszę na przykład zmienić klucze, a nie wartości. Dzięki, to pomocne.
Yogesh Patel
18

Podczas gdy większość odpowiedzi tutaj koncentruje się na mapowaniu całego słownika (kluczy i wartości), pytanie tak naprawdę dotyczyło tylko mapowania wartości. Jest to ważne rozróżnienie, ponieważ mapowanie wartości pozwala zagwarantować taką samą liczbę wpisów, podczas gdy mapowanie zarówno klucza, jak i wartości może skutkować zduplikowanymi kluczami.

Oto rozszerzenie, mapValuesktóre umożliwia mapowanie tylko wartości. Zauważ, że rozszerza również słownik o initz sekwencji par klucz / wartość, co jest nieco bardziej ogólne niż inicjowanie go z tablicy:

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

}
Prędkość powietrza
źródło
..Jak to jest używane? Jestem nowy w szybkim temacie. Umysł umieszczenia tutaj przykładu?
FlowUI. SimpleUITesting.com
kiedy próbowałem myDictionary.map, wewnątrz zamknięcia, musiałem normalnie zrobić inną mapę. To działa, ale czy tak powinno być używane?
FlowUI. SimpleUITesting.com
Czy istnieje gdzieś gwarancja, że ​​kolejność kluczy i wartości wywoływanych osobno jest sparowana, a zatem odpowiednia dla zip?
Aaron Zinman
Po co tworzyć nowy plik init, skoro można po prostu użyć func mapValues<T>(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }. Uważam, że to też lepiej się czyta. Czy występuje problem z wydajnością?
Marcel
12

Najprościej jest po prostu dodać mapdo Słownika:

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

Sprawdzanie, czy działa:

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)

Wynik:

[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]
Joseph Mark
źródło
1
Problem, który widzę w przypadku tego podejścia polega na tym, że SequenceType.mapnie jest to funkcja mutująca. Twój przykład mógłby zostać przepisany tak, aby był zgodny z istniejącą umową map, ale to jest to samo, co zaakceptowana odpowiedź.
Christopher Pickslay
7

Możesz także użyć reducezamiast mapy. reducejest w stanie zrobić wszystko, co mapmoże, a nawet więcej!

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]

Byłoby dość łatwo opakować to w rozszerzenie, ale nawet bez rozszerzenia pozwala robić to, co chcesz, za pomocą jednego wywołania funkcji.

Aaron Rasmussen
źródło
10
Wadą tego podejścia jest to, że za każdym razem, gdy dodajesz nowy klucz, tworzona jest zupełnie nowa kopia słownika, więc ta funkcja jest strasznie nieefektywna (optymalizator może cię uratować).
Prędkość lotu Kwietnia
To dobra uwaga ... jedna rzecz, której nie rozumiem zbyt dobrze, to wewnętrzne cechy zarządzania pamięcią Swifta. Doceniam takie komentarze, które każą mi o tym myśleć :)
Aaron Rasmussen
1
Nie ma potrzeby stosowania temp. Zmienna i redukuj jest teraz metodą w Swift 2.0:let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
Zmey
4

Okazuje się, że możesz to zrobić. To, co musisz zrobić, to utworzyć tablicę na podstawie MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>zwróconych przez keysmetodę słowników . ( Informacje tutaj ) Możesz następnie zmapować tę tablicę i przekazać zaktualizowane wartości z powrotem do słownika.

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

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

dictionary
Mick MacCallum
źródło
Czy możesz dodać wyjaśnienie, jak przekonwertować ["foo": 1, "bar": 2] na [("foo", 1), ("bar", 2)]
Maria Zverina
@MariaZverina Nie jestem pewien, co masz na myśli.
Mick MacCallum
Jeśli możesz wykonać powyższą konwersję, filtr można zastosować bezpośrednio do wynikowej tablicy
Maria Zverina
@MariaZverina Gotcha. Tak, niestety nie wiem, jak to zrobić.
Mick MacCallum
3

Zgodnie z podręcznikiem Swift Standard Library Reference, mapowanie jest funkcją tablic. Nie dla słowników.

Ale możesz iterować swój słownik, aby zmodyfikować klucze:

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

for (name, key) in d {
    d[name] = d[name]! + 1
}
Jens Wirth
źródło
3

Szukałem sposobu, aby zmapować słownik bezpośrednio do wpisanej tablicy z niestandardowymi obiektami. Znalazłem rozwiązanie w tym rozszerzeniu:

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

Używając go w następujący sposób:

self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in
    return VDLFilterValue(name: key, amount: value)
})
Antoine
źródło
3

Szybki 3

Próbuję na łatwy sposób w Swift 3.

Chcę zamapować [String: String?] Na [String: String] , używam forEach zamiast map lub 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
        }
    }
Kumpel
źródło
Niepożądane, ponieważ tworzy nową tablicę i dołącza do niej
Steve Kuo
2

Szybki 5

Funkcja mapy słownika ma taką składnię.

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

możesz po prostu ustawić wartości zamknięcia na to

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

Wynik będzie:

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

Może to być ostrzeżenie Result of call to 'map' is unused

Następnie wystarczy ustawić _ =przed zmienną.

Może to ci pomoże Dziękuję.

NøBi Mac
źródło
1

Zakładam, że problemem jest zachowanie kluczy naszego oryginalnego słownika i mapowanie w jakiś sposób wszystkich jego wartości.

Uogólnijmy i powiedzmy, że możemy chcieć odwzorować wszystkie wartości na inny typ .

Więc to, od czego chcielibyśmy zacząć, to słownik i zamknięcie (funkcja), a na końcu nowy słownik. Problem polega oczywiście na tym, że ścisłe pisanie Swifta przeszkadza. Zamknięcie musi określać, jaki typ wchodzi i jaki wychodzi, dlatego nie możemy utworzyć metody, która przyjmuje ogólne zamknięcie - z wyjątkiem kontekstu ogólnego. Dlatego potrzebujemy funkcji ogólnej.

Inne rozwiązania koncentrują się na robieniu tego w ogólnym świecie samej struktury ogólnej Dictionary, ale łatwiej mi jest myśleć w kategoriach funkcji najwyższego poziomu. Na przykład moglibyśmy napisać to, gdzie K jest typem klucza, V1 jest typem wartości słownika początkowego, a V2 jest typem wartości słownika końcowego:

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
}

Oto prosty przykład nazywania go, aby udowodnić, że rodzaj ogólny rzeczywiście rozwiązuje się sam w czasie kompilacji:

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

Zaczęliśmy od słownika, [String:Int]a skończyliśmy na słowniku [String:String], przekształcając wartości pierwszego słownika poprzez zamknięcie.

(EDYCJA: Teraz widzę, że jest to w rzeczywistości to samo, co rozwiązanie AirspeedVelocity, z wyjątkiem tego, że nie dodałem dodatkowej elegancji inicjalizatora słownika, który tworzy Dictionary z sekwencji zip.)

matowe
źródło
1

Szybki 3

Stosowanie:

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

Rozbudowa:

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
źródło
1

Próbujesz tylko zmapować wartości (potencjalnie zmieniając ich typ), dołącz to rozszerzenie:

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

Biorąc pod uwagę, że masz ten słownik:

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

Transformacja wartości jednowierszowej wygląda wtedy następująco:

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

Przekształcenie wartości wieloliniowej wygląda wówczas następująco:

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
źródło
0

Innym podejściem jest do mapsłownika i reduce, gdzie funkcje keyTransformi valueTransformsą funkcjami.

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
źródło
To była bardziej koncepcja niż rzeczywisty kod, ale mimo to rozszerzyłem go na działający kod.
NebulaFox
0

Swift 3 , użyłem tego,

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
}

Stosowanie:

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

Nr ref

Mohammad Zaid Pathan
źródło