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


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.