Как добавить словарь предметов в другой словарь

172

Массивы в Swift поддерживают оператор + = для добавления содержимого одного массива в другой. Есть ли простой способ сделать это для словаря?

например:

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

var combinedDict = ... (some way of combining dict1 & dict2 without looping)
rustyshelf
источник
fromDict.forEach {intoDict[$0] = $1}
Саззад Хисейн Хан

Ответы:

171

Вы можете определить +=оператор для Dictionary, например,

func += <K, V> (left: inout [K:V], right: [K:V]) { 
    for (k, v) in right { 
        left[k] = v
    } 
}
shucao
источник
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 Потому что мы должны сами реализовать половину языка? Да. Пораженный. Не поймите меня неправильно, я люблю перегрузку операторов. Я просто не люблю использовать его для базовых функций, которые должны быть встроены.
devios1
2
@devios Ха-ха, тогда сделайте запрос на повторное использование репозитория Swift: D Поскольку очевидно, что Apple не может быть арестована
CommaToast
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.

jasongregori
источник
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 будет использоваться в случае конфликта с ключами.

samwize
источник
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"]
mythz
источник
1
Вы также можете сделать это для + =, чтобы обновить на месте диктовку (согласно операционному вопросу).
Род
3
Вы можете покончить с mapпервым for (k, v)...циклом и отбросить его, если объявите leftпараметр как, varа затем просто скопируйте rightв него значения.
Нейт Кук
2
@NateCook, который будет изменять словарь, что не является ожидаемым поведением для +инфиксного оператора.
Миф
Спасибо за это. Ваш ответ, вероятно, был более точным для примера кода, который я разместил, в то время как другой был больше, чем я хотел, основываясь на моем вопросе. Мой плохой, в любом случае дал вам обоим upvote;)
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

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
    }
}
Олсен
источник
не может вызвать мутирующую функцию из такой функции, которая не является мутирующей
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/…
Даниэль Вуд
Чтобы сделать вещи более безопасными, добавьте <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"]
ricardopereira
источник
5
Я не понимаю, почему это не встроено в Swift по умолчанию?
ioquatix
1
Вы планируете значения слева направо, чтобы переопределить право в вашем «неизменном» решении? Я думаю, что вы хотите иметь right.reduce(left), по крайней мере, это ожидаемое поведение IMO (и это поведение вашего второго примера) - т.е. ["A":1] + ["A":2]должен вывести["A":2]
ccwasden
Выход соответствует коду. Я хочу, чтобы начальное значение было с правой стороны, как сейчас.
Рикардоперейра
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
    }    
}
orkoden
источник
3
очень хорошее и чистое решение!
user3441734 15.02.16
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
}
farhadf
источник
Это выглядит интересно, но что есть dи p?
ограбить
1
d - это постоянный результат каждой итерации блока сокращения, а p - элемент сокращаемой коллекции.
farhadf
1
это похоже на сбой в быстрой бета-версии 3.0
possen
Параметры var в swift устарели 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

Вы можете перебирать комбинации Key Value для значения, которое хотите объединить, и добавлять их с помощью метода updateValue (forKey :):

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

Теперь все значения словаря два добавлены в словарь один.

LEONS
источник
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
        }
    }

}
aaannjjaa
источник
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 . Просто импортируйте его в свой проект, и вы сможете использовать приведенный выше код, не добавляя в проект никаких расширений .

Jeehut
источник
Установка библиотеки для использования одной функции - плохая практика
HackaZach
@HackaZach: я только что обновил свой ответ, включив в него соответствующую часть структуры, чтобы предотвратить включение всей библиотеки, если требуется только эта небольшая часть. Я держу подсказку о фреймворке для людей, которые хотят использовать несколько его функций. Надеюсь, это поможет сохранить хорошую практику!
Jeehut
3

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

firstDictionary.merge(secondDictionary) { (value1, value2) -> AnyObject in
        return object2 // what you want to return if keys same.
    }
Бурак Дизлек
источник
2

Ты можешь использовать,

func addAll(from: [String: Any], into: [String: Any]){
    from.forEach {into[$0] = $1}
}
Саззад Хисейн Хан
источник
1

Вы можете использовать функцию bridgeToObjectiveC (), чтобы сделать словарь NSDictionary.

Будет похоже на следующее:

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

Все эти ответы сложны. Это мое решение для 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) 
Кевин ABRIOUX
источник
К сожалению, это работает только для NSMutableDictionary, а не для собственных словарей Swift. Я хотел бы, чтобы это было добавлено в 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

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
}
apinho
источник
если вы поставите это, вы можете удалить первый цикл: `var result = left`
NikeAlive
0

Я бы просто использовал библиотеку Dollar .

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]
Брайан Дж. Робертс
источник