Як додати словник елементів до іншого словника


172

Масиви в Swift підтримують оператор + =, щоб додати вміст одного масиву до іншого. Чи є простий спосіб зробити це для словника?

наприклад:

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

Відповіді:


171

Ви можете визначити +=оператора для Dictionary, наприклад,

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

1
О людино, я так боровся з пошуком належної загальної декларації для цього, я спробував усе, крім цього. Але ви можете кинути @assignmentі return, ви вже мутуєте ліворуч. Редагувати: насправді, хоча я не помиляюся, я думаю, що я @assignmentповинен залишатися.
Роланд

14
Більше синтаксичного цукру: func +=<K, V> (inout left: [K : V], right: [K : V]) { for (k, v) in right { left[k] = v } }
Іван Вавилов

48
@animal_chin Тому що ми повинні реалізувати половину мови самостійно? Так. Вражений. Не зрозумійте мене неправильно, я люблю операторські перевантаження. Мені просто не подобається використовувати його для основних функцій, які слід
вбудувати

2
@devios Haha зробить це запитом на швидке репортаж: D Оскільки очевидно, що Apple не може бути
змушений

6
Потягнувши прямо з бібліотеки SwifterSwift :public static func +=(lhs: inout [Key: Value], rhs: [Key: Value]) { rhs.forEach({ lhs[$0] = $1}) }
Джастін Ороз

99

У Swift 4 слід використовувати merging(_:uniquingKeysWith:):

Приклад:

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
Другий приклад, показаний у цій відповіді, є еквівалентом [NSMutableDictionary addEntriesFromDictionary:].
orj

92

Як щодо

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

Це додає всі ключі та значення dict2 у dict1.


43
Приємне рішення. Трохи коротше: dict2.forEach {dict1 [$ 0] = $ 1}
Бретт

1
Це чудове рішення, але для Swift 4 ви, швидше за все, отримаєте помилку із зазначенням Closure tuple parameter '(key: _, value: _)' does not support destructuring(принаймні, під час написання цього тексту). Один повинен був би реструктурувати закриття в відповідно до [цієї StackOverflow відповідь] ( stackoverflow.com/questions/44945967 / ... ):
JonnyB

78

Наразі, переглядаючи стандартну довідку бібліотеки Swift для словника, немає способу легко оновити словник іншим.

Ви можете написати розширення, щоб це зробити

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
Це чудове використання розширення для словника!
Марк Аттінасі

76

Swift 4 пропонує merging(_:uniquingKeysWith:)для вашого випадку:

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

Закриття скорочення повертається $1, тому значення dict2 буде використано, коли виникає конфлікт із ключами.


1
Я просто хотів зазначити, що це найкоротший і найбільш близький я знайшов те, про що йдеться в документації Apple - (void)addEntriesFromDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;. Що стосується того, що робити з дублікатами, він зазначає, що: "Якщо обидва словники містять один і той же ключ, попередній об'єкт значення словника для цього ключа надсилається повідомленням про звільнення, а новий об'єкт значення займає своє місце". версія Swift або в злитті (_: uniquingKeysWith :), повертаючи друге значення,, $1- це те саме, що і addEntriesFromDictionaryробить.
Тім Фукуа

31

Він не вбудований у бібліотеку Swift, але ви можете додати те, що хочете, при перевантаженні оператора, наприклад:

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
}

Це перевантажує +оператора для словників, який ви тепер можете використовувати для додавання словників з +оператором, наприклад:

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

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

1
Ви також можете зробити це для + =, щоб оновити місце диктату (відповідно до запитання op).
Прут

3
Ви можете усунути mapі скинути перший for (k, v)...цикл, якщо оголосите leftпараметр як, varа потім просто скопіюєте rightв нього значення.
Нейт Кук

2
@NateCook буде вимкнути Словник, що не очікується поведінки для +оператора infix.
mythz

Дякую за це. Ваша відповідь, ймовірно, була точнішою до зразкового коду, який я опублікував, а інший - більше того, що я хотів, виходячи з мого запитання. Мій поганий, все одно дав тобі і нагороду;)
rustyshelf

2
@mythz Це насправді не мутує, оскільки +перевантаження оператора - це не метод Dictionary, а проста функція. Зміни, які ви вносите в змінний leftпараметр, не будуть видимі поза функцією.
Нейт Кук

28

Швидкий 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
трохи кращеfunc merged(with dictionary: Dictionary<Key,Value>) -> Dictionary<Key,Value> { var copy = self dictionary.forEach { copy.updateValue($1, forKey: $0) } return copy }
Олександр Васенін

16

Швидкий 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
    }
}

не можу викликати функцію, що мутує, як такою, що не мутує,
njzk2

unionФункція має значення , передане в нього бути var, сенс скопійованого словника може бути мутував. Це трохи чистіше, ніж func union(dictionary: Dictionary) -> Dictionary { var dict2 = dictionary; dict2.unionInPlace(self); return dict2 }, хоч би на один рядок.
MaddTheSane

2
вар PARAMS застарів і треба увійти на сайт Swift 3. Переважний спосіб зробити це зараз , щоб оголосити вар в тілі: var dictionary = dictionary. Звідси: github.com/apple/swift-evolution/blob/master/proposals/…
Daniel Wood

Щоб зробити речі більш безпечними для типу, додайте <Key, Value>до них Dictionarys.
Рафаель

12

Незмінний

Я вважаю за краще поєднувати / об’єднувати незмінні словники з +оператором, тому я реалізував його так:

// 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"]

Змінні

// 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
Я не розумію, чому це не вбудовано у швидкий за замовчуванням?
ioquatix

1
чи маєте ви намір значення зліва перезаписати праворуч у вашому рішенні "Невідмінна"? Я думаю, ви маєте на увазі мати right.reduce(left), принаймні, це очікувана поведінка imo (і це поведінка вашого другого прикладу) - тобто. ["A":1] + ["A":2]повинен ["A":2]
вийти

Вихід відповідає коду. Я хочу, щоб початкове значення було правою стороною, як зараз.
ricardopereira

12

Зараз немає необхідності мати розширення словника. Словник Swift (Xcode 9.0+) має для цього функціонал. Погляньте тут . Нижче наведено приклад того, як його використовувати

  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
Я додаю функціональний стиль для наведеного вище прикладу:oldDictionary.merge(newDictionary) { $1 }
Андрій

11

Більш читаний варіант із використанням розширення.

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
дуже приємне і чисте рішення!
користувач3441734

11

Ви можете спробувати це

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

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

10

Ви також можете використовувати зменшити, щоб об'єднати їх. Спробуйте це на дитячому майданчику

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
}

Це виглядає цікаво, але що таке dі p?
пограбувати

1
d - постійний результат кожної ітерації блоку зменшення, а p - елемент колекції, який зменшується.
farhadf

1
це, здається,
зазнає

Параметри var застаріли у швидкому 3
Дмитро Клочков

Це моє улюблене рішення із згаданих тут. Фільтр / карта / зменшення перемог знову для чудових коротких рішень.
gokeji

7

Я рекомендую бібліотеку SwifterSwift . Однак якщо ви не хочете використовувати всю бібліотеку та всі її великі доповнення, ви можете просто скористатися їх розширенням словника:

Швидкий 3+

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

Насправді SE-110 було відновлено, тому версія Swift 4 повинна бути такою ж, як і версія Swift 3.
BallpointBen

5

Ви можете перебирати комбінації ключових значень у відношенні значення, яке потрібно об'єднати, та додати їх за допомогою методу updateValue (forKey :):

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

Тепер усі значення словника до словника додано два.


4

Те саме, що відповідь @ farhadf, але прийнято для 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, розширення словника:

public extension Dictionary {

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

}

4

Деякі навіть більш спрощені перевантаження для 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

Ви можете додати Dictionaryрозширення так:

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

Тоді використання настільки ж просто, як і наступне:

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

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

Якщо ви віддаєте перевагу фреймворку, який також включає деякі зручніші функції, то замовіть HandySwift . Просто імпортуйте його до свого проекту, і ви можете використовувати вищезазначений код, не додаючи до нього жодних розширень .


Встановлення бібліотеки для використання однієї функції є поганою практикою
HackaZach

@HackaZach: Я щойно оновив свою відповідь, щоб включити відповідну частину рамки, щоб запобігти включенню всієї бібліотеки, якщо потрібна лише ця маленька частина. Я дотримуюся підказки для тих, хто хоче використовувати декілька його функцій. Я сподіваюся, що це допомагає зберегти добру практику!
Jeehut

3

Більше немає потреби в розширенні чи додаткових функціях. Ви можете написати так:

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


1

Ви можете використовувати функцію bridgeToObjectiveC (), щоб зробити словник NSD-словником.

Буде так:

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)

Тоді ви можете конвертувати NSDictionary (комбінувати) назад або робити що завгодно.


Вибачте, що саме ви маєте на увазі?
Антон

Просто вподобання. Здається, переплутане між мовами. Краще дотримуватися меж однієї мови, одночасно дозволяючи obj-c померти швидше.
TruMan1

2
Так, я опублікував цю відповідь буквально в той день, коли Свіфт оголосив ....... Тож була причина
Антон

1
import Foundation

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

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

У результаті виходить NSMutableDictionary не словник набраного Swift, але синтаксис його використання такий же ( out["a"] == 1у цьому випадку), тому у вас виникне проблема, лише якщо ви використовуєте сторонній код, який очікує словник Swift, або потрібна перевірка типу.

Коротка відповідь тут полягає в тому, що вам насправді доведеться циклічно. Навіть якщо ви не вводите це прямо, саме це буде робити метод, який ви викликаєте (addEntriesFromDictionary: тут). Я б запропонував, якщо вам трохи не зрозуміло, чому це було б так, ви повинні розглянути, як би ви з’єднали листяні вузли двох B-дерев.

Якщо вам насправді потрібен рідний тип словника Swift, я б запропонував:

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

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

Недоліком цього підходу є те, що індекс словника - хоч і робиться - може бути кілька разів перебудовуватися в циклі, тому на практиці це приблизно в 10 разів повільніше, ніж підхід NSMutableDictionary.


1

Усі ці відповіді є складними. Це моє рішення для швидкого 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) 

На жаль, це працює лише на NSMutableDictionary, а не на рідних словниках Swift. Я б хотів, щоб це було додано до Свіфта споконвічно.
Кріс Павелліо

0

Мої потреби були різними, мені потрібно було об’єднати неповні вкладені набори даних, не переробляючи їх.

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

Це було важче, ніж я хотів. Завдання полягало у відображенні від динамічного введення до статичного набору тексту, і я використовував протоколи для вирішення цього питання.

Також варто відзначити, що при використанні словника буквального синтаксису ви фактично отримуєте типи основи, які не підбирають розширення протоколу. Я перервав свої зусилля, щоб підтримати таких, оскільки не міг знайти перевірку рівномірності елементів колекції.

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

Швидкий 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
}

якщо ви поставите це, ви можете видалити перший цикл: `var result = left`
NikeAlive

0

Я б просто скористався бібліотекою доларів .

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

Об'єднує всі словники разом, і останній словник переосмислює значення заданої клавіші

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 повернувся!
Бен Сінклер

0

Ось приємне розширення, яке я написав ...

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

використовувати:

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

dict1 = dict1.mergeOnto(target: dict2)

Тоді dict1 буде змінено на

["cat": 5, "dog": 6, "rodent": 10]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.