如何深度合并两个Swift字典

7

我的问题很简单,我想知道如何对两个Swift字典(不是NSDictionary)进行深度合并。

let dict1 = [
    "a": 1,
    "b": 2,
    "c": [
        "d": 3
    ],
    "f": 2
]

let dict2 = [
    "b": 4,
    "c": [
        "e": 5
    ],
    "f": ["g": 6]
]

let dict3 = dict1.merge(dict2)

/* Expected:

dict3 = [
    "a": 1,
    "b": 4,
    "c": [
        "d": 3,
        "e": 5
    ],
    "f": ["g": 6]
]

*/

dict1dict2具有相同的键时,我希望值被替换,但如果该值是另一个字典,则希望它被递归合并。

这是我想要的解决方案:

protocol Mergeable {

    mutating func merge(obj: Self)

}

extension Dictionary: Mergeable {

    // if they have the same key, the new value is taken
    mutating func merge(dictionary: Dictionary) {
        for (key, value) in dictionary {
            let oldValue = self[key]

            if oldValue is Mergeable && value is Mergeable {
                var oldValue = oldValue as! Mergeable
                let newValue = value as! Mergeable

                oldValue.merge(newValue)

                self[key] = oldValue
            } else {
                self[key] = value
            }
        }
    }

}

但是它给了我这个错误:Protocol 'Mergeable' can only be used as a generic constraint because it has Self or associated type requirements

编辑: 我的问题不同于Swift: how to combine two Dictionary instances?因为那个不是深度合并。

使用那个解决方案,它会产生:

dict3 = [
    "a": 1,
    "b": 4,
    "c": [
        "e": 5
    ]
]

1
可能是重复的问题:Swift:如何合并两个字典数组? - Abhishek Dey
1
那个不是深度合并。 - Rodrigo Ruiz
它可以是任何东西,这就是问题所在,它不一定只有两个层级和字符串和数字。 - Rodrigo Ruiz
还有,请告诉我:如果“c”在一个字典中的值是字符串,在另一个字典中是字典,现在怎么办?我试图向您展示您的规范不连贯。 - matt
让字典等于[String: AnyObject]... - Rodrigo Ruiz
显示剩余3条评论
3个回答

8

在我看来,这个问题不太清晰。然而,以下是一个答案:

func deepMerge(d1:[String:AnyObject], _ d2:[String:AnyObject]) -> [String:AnyObject] {
    var result = [String:AnyObject]()
    for (k1,v1) in d1 {
        result[k1] = v1
    }
    for (k2,v2) in d2 {
        if v2 is [String:AnyObject], let v1 = result[k2] where v1 is [String:AnyObject] {
            result[k2] = deepMerge(v1 as! [String:AnyObject],v2 as! [String:AnyObject])
        } else {
            result[k2] = v2
        }
    }
    return result
}

以下是您的测试用例:

    let dict1:[String:AnyObject] = [
        "a": 1,
        "b": 2,
        "c": [
            "d": 3
        ]
    ]

    let dict2:[String:AnyObject] = [
        "b": 4,
        "c": [
            "e": 5
        ]
    ]
    let result = deepMerge(dict1, dict2)
    NSLog("%@", result)
    /*
    {
        a = 1;
        b = 4;
        c =     {
            d = 3;
            e = 5;
        };
    }
    */

第三方编辑: 使用变量绑定和较新的Swift语法的备用版本。

func deepMerge(_ d1: [String: Any], _ d2: [String: Any]) -> [String: Any] {
    var result = d1
    for (k2, v2) in d2 {
        if let v1 = result[k2] as? [String: Any], let v2 = v2 as? [String: Any] {
            result[k2] = deepMerge(v1, v2)
        } else {
            result[k2] = v2
        }
    }
    return result
}

谢谢,实际上尝试您的解决方案(我认为与我的相同)让我意识到将字典与[String:AnyObject?]进行比较是行不通的,我必须与[String:AnyObject]进行比较。只有一个问题,如何接受[String:AnyObject?]。 - Rodrigo Ruiz
我已经说过这个问题不连贯。我已经回答了你所问的问题。我不会在这里逗留,而你不断地改变目标。 - matt
这个问题为什么不连贯?我想要一个字典的深度合并,就像在任何脚本语言中一样... - Rodrigo Ruiz
1
这个问题一点也不含糊,这是一个好的解决方案。 :-) - Quinn Taylor

1
extension Dictionary {
    mutating func deepMerge(_ dict: Dictionary) {
        merge(dict) { (current, new) in
            if var currentDict = current as? Dictionary, let newDict = new as? Dictionary {
                currentDict.deepMerge(newDict)
                return currentDict as! Value
            }
            return new
        }
    }
}

如何使用/测试:
var dict1: [String: Any] = [
    "a": 1,
    "b": 2,
    "c": [
        "d": 3
    ],
    "f": 2
]

var dict2: [String: Any] = [
    "b": 4,
    "c": [
        "e": 5
    ],
    "f": ["g": 6]
]

dict1.deepMerge(dict2)

print(dict1)

这个例子不能正常工作。我已经测试过了。问题似乎是合并中的替换没有正常工作。尝试用 "f": ["g": 2](在 dict1 中)替换 "f": 2,你将不会在日志输出中收到 "f": ["g": 6],而是旧值(错误的)"f": ["g": 2]。我不会给你点踩,但请修复它。 - Darkwonder
是的,你是对的,已经修复并更新了答案。请验证。测试代码可以在这里找到:https://github.com/river2202/MonorailSwift/blob/0f6aaf12706f2a4f47ec735c084a38998fad5cab/MonorailSwiftTests/MonorailSwiftTests.swift#L84 - River2202

0

手动操作如何?

func += <KeyType, ValueType> (inout left: Dictionary<KeyType, ValueType>, right: Dictionary<KeyType, ValueType>) { 
    for (k, v) in right { 
        left.updateValue(v, forKey: k) 
    } 
}

你也可以尝试使用ExSwift库

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接