Jak dodać słownik elementów do innego słownika


172

Tablice w języku Swift obsługują operator + = w celu dodania zawartości jednej tablicy do drugiej. Czy istnieje łatwy sposób na zrobienie tego w przypadku słownika?

na przykład:

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

var combinedDict = ... (some way of combining dict1 & dict2 without looping)


fromDict.forEach {intoDict[$0] = $1}
Sazzad Hissain Khan

Odpowiedzi:


171

Możesz zdefiniować +=operator Dictionarynp.

func += <K, V> (left: inout [K:V], right: [K:V]) { 
    for (k, v) in right { 
        left[k] = v
    } 
}

1
O rany, tak bardzo walczyłem ze znalezieniem odpowiedniej ogólnej deklaracji, próbowałem wszystkiego oprócz tego. Ale możesz porzucić @assignmenti return, już mutujesz left. Edycja: właściwie, chociaż nie dostaję żadnych błędów, myślę, że @assignmentpowinienem zostać.
Roland,

14
Więcej cukru składniowego: func +=<K, V> (inout left: [K : V], right: [K : V]) { for (k, v) in right { left[k] = v } }
Ivan Vavilov

48
@animal_chin Ponieważ musimy sami wdrożyć połowę języka? Tak. Pod wrażeniem. Nie zrozum mnie źle Uwielbiam przeciążanie operatorów. Po prostu nie lubię używać go do podstawowych funkcji, które powinny być wbudowane.
devios1

2
@devios Haha następnie zrobić to wyciągnąć wniosek do repo Swift: D Od widocznie Apple nie może być arsed
CommaToast

6
Ciągnięcie prosto z Biblioteki SwifterSwift :public static func +=(lhs: inout [Key: Value], rhs: [Key: Value]) { rhs.forEach({ lhs[$0] = $1}) }
Justin Oroz,

99

W Swift 4 należy używać merging(_:uniquingKeysWith:):

Przykład:

let dictA = ["x" : 1, "y": 2, "z": 3]
let dictB = ["x" : 11, "y": 22, "w": 0]

let resultA = dictA.merging(dictB, uniquingKeysWith: { (first, _) in first })
let resultB = dictA.merging(dictB, uniquingKeysWith: { (_, last) in last })

print(resultA) // ["x": 1, "y": 2, "z": 3, "w": 0]
print(resultB) // ["x": 11, "y": 22, "z": 3, "w": 0]

1
// mutable: var dictA = ["x": 1, "y": 2, "z": 3] var dictB = ["x": 11, "y": 22, "w": 0] dictA. merge (dictB, uniquingKeysWith: {(first, _) in first}) print (dictA) // ["x": 1, "y": 2, "z": 3, "w": 0]
muthukumar

1
Drugi przykład pokazany w tej odpowiedzi jest odpowiednikiem [NSMutableDictionary addEntriesFromDictionary:].
orj

92

Co powiesz na

dict2.forEach { (k,v) in dict1[k] = v }

To dodaje wszystkie klucze i wartości dict2 do dict1.


43
Niezłe rozwiązanie. Nieco krótszy: dict2.forEach {dict1 [$ 0] = 1 $}
Brett

1
To świetne rozwiązanie, ale w przypadku Swift 4 najprawdopodobniej wystąpi błąd z informacją Closure tuple parameter '(key: _, value: _)' does not support destructuring(przynajmniej w momencie pisania tego tekstu). Trzeba by
zmienić

78

Obecnie, patrząc na Swift Standard Library Reference for Dictionary, nie ma możliwości łatwej aktualizacji słownika innym.

Możesz napisać rozszerzenie, aby to zrobić

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

extension Dictionary {
    mutating func update(other:Dictionary) {
        for (key,value) in other {
            self.updateValue(value, forKey:key)
        }
    }
}

dict1.update(dict2)
// dict1 is now ["a" : "foo", "b" : "bar]

3
To świetne zastosowanie rozszerzenia Słownika!
Marc Attinasi

76

Swift 4 zapewnia merging(_:uniquingKeysWith:), więc w Twoim przypadku:

let combinedDict = dict1.merging(dict2) { $1 }

Zwraca skrótowe zamknięcie $1, dlatego wartość dict2 zostanie użyta w przypadku konfliktu z kluczami.


1
Chciałem tylko zwrócić uwagę, że jest to najbardziej zwięzłe i najbliższe temu, o czym mówi dokumentacja Apple - (void)addEntriesFromDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;. W odniesieniu do tego, co zrobić z duplikatami, stwierdza on, że: „Jeśli oba słowniki zawierają ten sam klucz, poprzedni obiekt wartości słownika odbierającego dla tego klucza otrzymuje komunikat o zwolnieniu, a nowy obiekt wartości zajmuje jego miejsce.”, Więc w wersja Swift lub w merge (_: uniquingKeysWith :), zwracająca drugą wartość $1, jest tym samym, co addEntriesFromDictionaryrobi.
Tim Fuqua,

31

Nie jest wbudowany w bibliotekę Swift, ale możesz dodać to, co chcesz, z przeciążeniem operatora, np:

func + <K,V>(left: Dictionary<K,V>, right: Dictionary<K,V>) 
    -> Dictionary<K,V> 
{
    var map = Dictionary<K,V>()
    for (k, v) in left {
        map[k] = v
    }
    for (k, v) in right {
        map[k] = v
    }
    return map
}

Powoduje to przeciążenie +operatora dla słowników, których możesz teraz użyć do dodania słowników za pomocą +operatora, np .:

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

var dict3 = dict1 + dict2 // ["a": "foo", "b": "bar"]

1
Możesz to również zrobić dla + =, aby zaktualizować w miejscu dykt (zgodnie z pytaniem op).
Rod

3
Możesz pozbyć się mapi upuścić pierwszą for (k, v)...pętlę, jeśli zadeklarujesz leftparametr jako, vara następnie po prostu skopiujesz z rightniego wartości .
Nate Cook

2
@NateCook, który zmutowałby słownik, co nie jest oczekiwanym zachowaniem dla +operatora infix.
mythz

Dziękuję za to. Twoja odpowiedź prawdopodobnie była dokładniejsza w stosunku do przykładowego kodu, który opublikowałem, podczas gdy druga była bardziej zgodna z moim pytaniem. Moja wina, tak czy owak, dałem wam obu pochwałę;)
rustyshelf

2
@mythz To nie jest tak naprawdę mutacja, ponieważ +przeciążenie operatora nie jest metodą Dictionary, jest to prosta funkcja. Zmiany wprowadzone w leftparametrze zmiennej nie byłyby widoczne poza funkcją.
Nate Cook

28

Swift 3:

extension Dictionary {

    mutating func merge(with dictionary: Dictionary) {
        dictionary.forEach { updateValue($1, forKey: $0) }
    }

    func merged(with dictionary: Dictionary) -> Dictionary {
        var dict = self
        dict.merge(with: dictionary)
        return dict
    }
}

let a = ["a":"b"]
let b = ["1":"2"]
let c = a.merged(with: b)

print(c) //["a": "b", "1": "2"]

6
nieco lepiejfunc merged(with dictionary: Dictionary<Key,Value>) -> Dictionary<Key,Value> { var copy = self dictionary.forEach { copy.updateValue($1, forKey: $0) } return copy }
Alexander Vasenin

16

Swift 2.0

extension Dictionary {

    mutating func unionInPlace(dictionary: Dictionary) {
        dictionary.forEach { self.updateValue($1, forKey: $0) }
    }

    func union(var dictionary: Dictionary) -> Dictionary {
        dictionary.unionInPlace(self)
        return dictionary
    }
}

nie można wywołać funkcji
mutującej z niezmutowanej

unionFunkcja ma wartość przekazany do być var, czyli skopiowany słownika może być zmutowany. Jest trochę czystszy niż func union(dictionary: Dictionary) -> Dictionary { var dict2 = dictionary; dict2.unionInPlace(self); return dict2 }, choćby o jedną linię.
MaddTheSane,

2
var params są przestarzałe i zostaną usunięte w Swift 3. Preferowanym sposobem osiągnięcia tego celu jest teraz zadeklarować var w organizmie: var dictionary = dictionary. Stąd: github.com/apple/swift-evolution/blob/master/propiments/…
Daniel Wood

Aby zwiększyć bezpieczeństwo typów, dodaj <Key, Value>do tych Dictionarys.
Raphael,

12

Niezmienny

Wolę łączyć / łączyć niezmienne słowniki z +operatorem, więc zaimplementowałem to tak:

// Swift 2
func + <K,V> (left: Dictionary<K,V>, right: Dictionary<K,V>?) -> Dictionary<K,V> {
    guard let right = right else { return left }
    return left.reduce(right) {
        var new = $0 as [K:V]
        new.updateValue($1.1, forKey: $1.0)
        return new
    }
}

let moreAttributes: [String:AnyObject] = ["Function":"authenticate"]
let attributes: [String:AnyObject] = ["File":"Auth.swift"]

attributes + moreAttributes + nil //["Function": "authenticate", "File": "Auth.swift"]    
attributes + moreAttributes //["Function": "authenticate", "File": "Auth.swift"]
attributes + nil //["File": "Auth.swift"]

Zmienny

// Swift 2
func += <K,V> (inout left: Dictionary<K,V>, right: Dictionary<K,V>?) {
    guard let right = right else { return }
    right.forEach { key, value in
        left.updateValue(value, forKey: key)
    }
}

let moreAttributes: [String:AnyObject] = ["Function":"authenticate"]
var attributes: [String:AnyObject] = ["File":"Auth.swift"]

attributes += nil //["File": "Auth.swift"]
attributes += moreAttributes //["File": "Auth.swift", "Function": "authenticate"]

5
Nie rozumiem, dlaczego nie jest to domyślnie wbudowane w Swift?
ioquatix

1
Czy zamierzasz zastąpić wartości z lewej strony w rozwiązaniu „Niezmiennym”? Myślę, że chcesz mieć right.reduce(left), przynajmniej takie jest oczekiwane zachowanie imo (i jest to zachowanie z twojego drugiego przykładu) - tj. ["A":1] + ["A":2]powinno wyjść["A":2]
ccwasden

Dane wyjściowe odpowiadają kodowi. Chcę, aby wartość początkowa była po prawej stronie, tak jak jest teraz.
ricardopereira

12

Nie ma teraz potrzeby posiadania żadnych rozszerzeń słownika. Słownik Swift (Xcode 9.0+) posiada odpowiednią funkcjonalność. Zajrzyj tutaj . Poniżej znajduje się przykład, jak go używać

  var oldDictionary = ["a": 1, "b": 2]
  var newDictionary = ["a": 10000, "b": 10000, "c": 4]

  oldDictionary.merge(newDictionary) { (oldValue, newValue) -> Int in
        // This closure return what value to consider if repeated keys are found
        return newValue 
  }
  print(oldDictionary) // Prints ["b": 10000, "a": 10000, "c": 4]

2
Dodaję funkcjonalny styl do powyższego przykładu:oldDictionary.merge(newDictionary) { $1 }
Andrej

11

Bardziej czytelny wariant z rozszerzeniem.

extension Dictionary {    
    func merge(dict: Dictionary<Key,Value>) -> Dictionary<Key,Value> {
        var mutableCopy = self        
        for (key, value) in dict {
            // If both dictionaries have a value for same key, the value of the other dictionary is used.           
            mutableCopy[key] = value 
        }        
        return mutableCopy
    }    
}

3
bardzo ładne i czyste rozwiązanie!
user3441734

11

Możesz tego spróbować

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

var temp = NSMutableDictionary(dictionary: dict1);
temp.addEntriesFromDictionary(dict2)

10

Aby je scalić, możesz również użyć funkcji Redukcja. Spróbuj tego na placu zabaw

let d1 = ["a":"foo","b":"bar"]
let d2 = ["c":"car","d":"door"]

let d3 = d1.reduce(d2) { (var d, p) in
   d[p.0] = p.1
   return d
}

To wygląda interesująco, ale jakie są di p?
okraść

1
d jest trwałym wynikiem każdej iteracji bloku redukuj, ap jest elementem kolekcji, która jest redukowana.
farhadf

1
wydaje się, że zawieszenie się w szybkiej wersji beta 3.0
możliwe

Parametry var są przestarzałe w swift 3
Dmitry Klochkov

To moje ulubione rozwiązanie spośród wymienionych tutaj. Ponownie filtruj / mapuj / zmniejsz wygrane, aby uzyskać świetne zwięzłe rozwiązania.
gokeji

7

Polecam bibliotekę SwifterSwift . Jeśli jednak nie chcesz korzystać z całej biblioteki i wszystkich jej wspaniałych dodatków, możesz po prostu skorzystać z ich rozszerzenia Słownik:

Swift 3+

public extension Dictionary {
    public static func +=(lhs: inout [Key: Value], rhs: [Key: Value]) {
        rhs.forEach({ lhs[$0] = $1})
    }
}

W rzeczywistości SE-110 został przywrócony, więc wersja Swift 4 powinna być taka sama jak wersja Swift 3.
BallpointBen

5

Możesz iterować po kombinacjach Key Value dla wartości, którą chcesz scalić i dodać je za pomocą metody updateValue (forKey :):

dictionaryTwo.forEach {
    dictionaryOne.updateValue($1, forKey: $0)
}

Teraz wszystkie wartości słownikTwo zostały dodane do DictionaryOne.


4

To samo, co odpowiedź @ farhadf, ale przyjęte dla Swift 3:

let sourceDict1 = [1: "one", 2: "two"]
let sourceDict2 = [3: "three", 4: "four"]

let result = sourceDict1.reduce(sourceDict2) { (partialResult , pair) in
    var partialResult = partialResult //without this line we could not modify the dictionary
    partialResult[pair.0] = pair.1
    return partialResult
}

4

Swift 3, rozszerzenie słownika:

public extension Dictionary {

    public static func +=(lhs: inout Dictionary, rhs: Dictionary) {
        for (k, v) in rhs {
            lhs[k] = v
        }
    }

}

4

Niektóre jeszcze bardziej usprawnione przeciążenia dla Swift 4:

extension Dictionary {
    static func += (lhs: inout [Key:Value], rhs: [Key:Value]) {
        lhs.merge(rhs){$1}
    }
    static func + (lhs: [Key:Value], rhs: [Key:Value]) -> [Key:Value] {
        return lhs.merging(rhs){$1}
    }
}

3

Możesz dodać takie Dictionaryrozszerzenie :

extension Dictionary {
    func mergedWith(otherDictionary: [Key: Value]) -> [Key: Value] {
        var mergedDict: [Key: Value] = [:]
        [self, otherDictionary].forEach { dict in
            for (key, value) in dict {
                mergedDict[key] = value
            }
        }
        return mergedDict
    }
}

Wtedy użycie jest tak proste, jak następujące:

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

var combinedDict = dict1.mergedWith(dict2)
// => ["a": "foo", "b": "bar"]

Jeśli wolisz ramy, która zawiera również kilka bardziej przydatne funkcje potem kasa HandySwift . Po prostu zaimportuj go do swojego projektu i możesz użyć powyższego kodu bez samodzielnego dodawania jakichkolwiek rozszerzeń do projektu.


Instalacja biblioteki do korzystania z jednej funkcji jest złą praktyką
HackaZach

@HackaZach: Właśnie zaktualizowałem swoją odpowiedź, dodając odpowiednią część frameworka, aby zapobiec włączeniu całej biblioteki, jeśli tylko ta mała część jest potrzebna. Zachowuję wskazówkę dotyczącą frameworka dla osób, które chcą korzystać z wielu jego funkcji. Mam nadzieję, że pomoże to zachować dobre praktyki!
Jeehut

3

Nie ma już potrzeby rozbudowy ani żadnej dodatkowej funkcji. Możesz napisać tak:

firstDictionary.merge(secondDictionary) { (value1, value2) -> AnyObject in
        return object2 // what you want to return if keys same.
    }


1

Możesz użyć funkcji bridgeToObjectiveC (), aby uczynić słownik NSDictionary.

Będzie wyglądać następująco:

var dict1 = ["a":"Foo"]
var dict2 = ["b":"Boo"]

var combinedDict = dict1.bridgeToObjectiveC()
var mutiDict1 : NSMutableDictionary! = combinedDict.mutableCopy() as NSMutableDictionary

var combineDict2 = dict2.bridgeToObjectiveC()

var combine = mutiDict1.addEntriesFromDictionary(combineDict2)

Następnie możesz przekonwertować NSDictionary (połączyć) z powrotem lub zrobić cokolwiek.


Przepraszam, co dokładnie masz na myśli?
Anton,

Tylko preferencja. Wydaje się, że pomost między językami jest skomplikowany. Lepiej trzymać się ograniczeń jednego języka, jednocześnie pozwalając przedmiotowi umierać szybciej.
TruMan1,

2
Tak, opublikowałem tę odpowiedź dosłownie w dniu, w którym ogłosił Swift ....... Więc był powód
Anton

1
import Foundation

let x = ["a":1]
let y = ["b":2]

let out = NSMutableDictionary(dictionary: x)
out.addEntriesFromDictionary(y)

Wynikiem jest NSMutableDictionary, a nie słownik typu Swift, ale jego składnia jest taka sama (out["a"] == 1 w tym przypadku), więc masz problem tylko wtedy, gdy używasz kodu innej firmy, który oczekuje słownika Swift, lub naprawdę Potrzebujesz sprawdzenia typu.

Krótka odpowiedź jest taka, że ​​faktycznie musisz zapętlić. Nawet jeśli nie wprowadzasz go jawnie, zrobi to wywoływana metoda (addEntriesFromDictionary: tutaj). Zasugerowałbym, jeśli nie masz pewności, dlaczego tak się dzieje, powinieneś rozważyć, w jaki sposób połączyć węzły liściowe dwóch drzew B.

Jeśli naprawdę potrzebujesz w zamian natywnego typu słownika Swift, proponuję:

let x = ["a":1]
let y = ["b":2]

var out = x
for (k, v) in y {
    out[k] = v
}

Wadą tego podejścia jest to, że indeks słownika - jakkolwiek jest to zrobione - może być wielokrotnie przebudowywany w pętli, więc w praktyce jest to około 10x wolniejsze niż podejście NSMutableDictionary.


1

Wszystkie te odpowiedzi są skomplikowane. To jest moje rozwiązanie dla Swift 2.2:

    //get first dictionnary
    let finalDictionnary : NSMutableDictionary = self.getBasicDict()
    //cast second dictionnary as [NSObject : AnyObject]
    let secondDictionnary : [NSObject : AnyObject] = self.getOtherDict() as [NSObject : AnyObject]
    //merge dictionnary into the first one
    finalDictionnary.addEntriesFromDictionary(secondDictionnary) 

Niestety działa to tylko na NSMutableDictionary, a nie na natywnych słownikach Swift. Chciałbym, żeby to zostało dodane do Swifta natywnie.
Chris Paveglio,

0

Moje potrzeby były inne, musiałem scalić niekompletne zagnieżdżone zestawy danych bez klęczenia.

merging:
    ["b": [1, 2], "s": Set([5, 6]), "a": 1, "d": ["x": 2]]
with
    ["b": [3, 4], "s": Set([6, 7]), "a": 2, "d": ["y": 4]]
yields:
    ["b": [1, 2, 3, 4], "s": Set([5, 6, 7]), "a": 2, "d": ["y": 4, "x": 2]]

To było trudniejsze niż chciałem. Wyzwanie polegało na odwzorowaniu z dynamicznego pisania na statyczne i użyłem protokołów, aby rozwiązać ten problem.

Warto również zauważyć, że kiedy używasz składni literału słownikowego, w rzeczywistości otrzymujesz typy podstawowe, które nie odbierają rozszerzeń protokołu. Zrezygnowałem z wysiłków, aby je wspierać, ponieważ nie mogłem znaleźć łatwego sposobu na sprawdzenie jednolitości elementów kolekcji.

import UIKit


private protocol Mergable {
    func mergeWithSame<T>(right: T) -> T?
}



public extension Dictionary {

    /**
    Merge Dictionaries

    - Parameter left: Dictionary to update
    - Parameter right:  Source dictionary with values to be merged

    - Returns: Merged dictionay
    */


    func merge(right:Dictionary) -> Dictionary {
        var merged = self
        for (k, rv) in right {

            // case of existing left value
            if let lv = self[k] {

                if let lv = lv as? Mergable where lv.dynamicType == rv.dynamicType {
                    let m = lv.mergeWithSame(rv)
                    merged[k] = m
                }

                else if lv is Mergable {
                    assert(false, "Expected common type for matching keys!")
                }

                else if !(lv is Mergable), let _ = lv as? NSArray {
                    assert(false, "Dictionary literals use incompatible Foundation Types")
                }

                else if !(lv is Mergable), let _ = lv as? NSDictionary {
                    assert(false, "Dictionary literals use incompatible Foundation Types")
                }

                else {
                    merged[k] = rv
                }
            }

                // case of no existing value
            else {
                merged[k] = rv
            }
        }

        return merged
    }
}




extension Array: Mergable {

    func mergeWithSame<T>(right: T) -> T? {

        if let right = right as? Array {
            return (self + right) as? T
        }

        assert(false)
        return nil
    }
}


extension Dictionary: Mergable {

    func mergeWithSame<T>(right: T) -> T? {

        if let right = right as? Dictionary {
            return self.merge(right) as? T
        }

        assert(false)
        return nil
    }
}


extension Set: Mergable {

    func mergeWithSame<T>(right: T) -> T? {

        if let right = right as? Set {
            return self.union(right) as? T
        }

        assert(false)
        return nil
    }
}



var dsa12 = Dictionary<String, Any>()
dsa12["a"] = 1
dsa12["b"] = [1, 2]
dsa12["s"] = Set([5, 6])
dsa12["d"] = ["c":5, "x": 2]


var dsa34 = Dictionary<String, Any>()
dsa34["a"] = 2
dsa34["b"] = [3, 4]
dsa34["s"] = Set([6, 7])
dsa34["d"] = ["c":-5, "y": 4]


//let dsa2 = ["a": 1, "b":a34]
let mdsa3 = dsa12.merge(dsa34)
print("merging:\n\t\(dsa12)\nwith\n\t\(dsa34) \nyields: \n\t\(mdsa3)")

0

Swift 2.2

func + <K,V>(left: [K : V], right: [K : V]) -> [K : V] {
    var result = [K:V]()

    for (key,value) in left {
        result[key] = value
    }

    for (key,value) in right {
        result[key] = value
    }
    return result
}

jeśli to umieścisz, możesz usunąć pierwszą pętlę: `var result = left`
NikeAlive

0

Po prostu skorzystałbym z biblioteki Dollar .

https://github.com/ankurp/Dollar/#merge---merge-1

Łączy wszystkie słowniki razem, a ten drugi słownik zastępuje wartość w danym kluczu

let dict: Dictionary<String, Int> = ["Dog": 1, "Cat": 2]
let dict2: Dictionary<String, Int> = ["Cow": 3]
let dict3: Dictionary<String, Int> = ["Sheep": 4]
$.merge(dict, dict2, dict3)
=> ["Dog": 1, "Cat": 2, "Cow": 3, "Sheep": 4]

5
jQuery powraca yay!
Ben Sinclair,

0

Oto fajne rozszerzenie, które napisałem ...

extension Dictionary where Value: Any {
    public func mergeOnto(target: [Key: Value]?) -> [Key: Value] {
        guard let target = target else { return self }
        return self.merging(target) { current, _ in current }
    }
}

używać:

var dict1 = ["cat": 5, "dog": 6]
var dict2 = ["dog": 9, "rodent": 10]

dict1 = dict1.mergeOnto(target: dict2)

Następnie dict1 zostanie zmieniony na

["cat": 5, "dog": 6, "rodent": 10]
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.