【问题标题】:Generic function to merge two dictionaries合并两个字典的通用函数
【发布时间】:2016-06-10 06:09:49
【问题描述】:

我想要一个通用函数来合并两个字典。它需要比How do you add a Dictionary of items into another Dictionary 中描述的更通用。原因:我的字典中有对象,我只想更新特定属性而不是整个对象本身。 (具体来说,它们是具有数组属性的对象。如果对象存在,我需要附加到该数组,或者使用新数组创建一个新对象。我不能简单地检查并相应地覆盖整个对象。我我对它的属性感兴趣。)

我尝试使用函数式编程来做到这一点:

extension Dictionary {
    func merge(withDictionary: Dictionary) -> Dictionary {
        var returnDictionary = withDictionary   // make a copy of dictionary (a dictionary is a struct in Swift)
        // Merge self dictionary into returnDictionary
        for key in self.keys {
            // If there is already a value associated for this key. (In my concrete case I will need to append to the value object's list property.)
            if let withDictionaryValue = returnDictionary[key], selfValue = self[key] {

                // I NEED TO DO THIS HERE.
                //
                // returnDictionary[key]!.list = withDictionaryValue.list + selfValue.list
                //
                // CAN'T FIGURE OUT HOW TO DO THIS IN A GENERIC WAY. THIS GENERIC MERGE SHOULDN'T NEED TO KNOW THAT THIS PARTICULAR DICTIONARY HAS VALUE OBJECTS THAT CONTAIN A 'list' PROPERTY.
                // HOW DO I PASS THIS CODE SNIPPET LINE IN AS PART OF A CLOSURE, WHICH USES 'withDictionaryValue' AND 'selfValue'?

            } else {
                // Simply write into this key - it doesn't yet contain values.
                returnDictionary[key] = self[key]
            }
        }

        return returnDictionary

    }

}

【问题讨论】:

    标签: swift dictionary merge functional-programming


    【解决方案1】:

    此通用合并不需要知道此特定字典具有包含“列表”属性的值对象。

    相反,为了让您的函数访问字典值的list 属性,您需要告诉编译器这些值具有list 属性。您可以通过创建协议并将扩展限制为仅对具有符合此协议的值的字典进行操作来做到这一点:

    // your protocol that defines the list property
    protocol ListType {
        var list : [AnyObject] { get set }
    }
    
    extension Dictionary where Value : ListType {
        func merge(withDictionary: Dictionary) -> Dictionary {
            var returnDictionary = withDictionary   // make a copy of dictionary (a dictionary is a struct in Swift)
    
            for (key, value) in self { // iterate through key value pairs
                if let withDictionaryValue = returnDictionary[key] { // if value exists, merge the list properties
                    returnDictionary[key]!.list = value.list + withDictionaryValue.list
                } else {
                    returnDictionary[key] = value
                }
            }
            return returnDictionary
        }
    }
    

    然后,您可以直接或通过扩展使您正在使用的值类型符合此协议。

    struct Foo : ListType {
        var list: [AnyObject]
    }
    

    let d = ["foo" : Foo(list: ["foo", "bar", "baz"])]
    let d1 = ["foo" : Foo(list: ["qux", "blah", "blue"])]
    
    let r = d.merge(d1) // ["foo": Foo(list: [foo, bar, baz, qux, blah, blue])]
    

    如果您想要更通用的方法,您可以定义一个Mergable 协议,该协议定义了一个方法,其中符合类型可以执行自己的合并逻辑。然后,如果值符合协议,您将更改您的扩展以调用此方法,否则只需合并键值对。

    protocol Mergable {
        func merge(withOther:Self) -> Self
    }
    
    // if values are Mergable (and the key-value pairs exist in both dictionaries), then call their custom logic for merging
    extension Dictionary where Value : Mergable {
        func merge(withDictionary: Dictionary) -> Dictionary {
            var returnDictionary = withDictionary
            for (key, value) in self {
                if let withDictionaryValue = withDictionary[key] {
                    returnDictionary[key] = value.merge(withDictionaryValue)
                } else {
                    returnDictionary[key] = value
                }
            }
            return returnDictionary
        }
    }
    
    // standard merging logic
    extension Dictionary {
        func merge(withDictionary: Dictionary) -> Dictionary {
            var returnDictionary = withDictionary
            keys.forEach {returnDictionary[$0] = self[$0]}
            return returnDictionary
        }
    }
    

    然后你可以像这样使你的值类型符合这个协议:

    // Foo's custom merging logic
    extension Foo : Mergable {
        func merge(withOther: Foo) -> Foo {
            var merged = self
    
            // merge the list array
            merged.list.appendContentsOf(withOther.list)
    
            return merged
        }
    }
    

    如果您的字典的值为Mergable,那么编译器将支持执行自定义合并逻辑的扩展,因为首选类型更具体的签名。


    在回复您关于希望使用闭包执行此操作的评论时,您可以merge 函数添加一个闭包参数,该函数将传递对第一个值的伪引用(通过使用 @987654331 @) 以及第二个值,允许您在调用函数时更改第一个值。

    extension Dictionary {
        func merge(withDictionary: Dictionary, @noescape merge: (value: inout Value, withValue: Value) -> ()) -> Dictionary {
            var returnDictionary = withDictionary   // make a copy of dictionary (a dictionary is a struct in Swift)
    
            for (key, value) in self { // iterate through key value pairs
                if let withDictionaryValue = returnDictionary[key] {
    
                    // create mutable copy of the value
                    var value = value
                    merge(value:&value, withValue:withDictionaryValue) // invoke closure to write merging changes to the value
                    returnDictionary[key] = value // assign value
    
                } else {
                    returnDictionary[key] = value
                }
            }
            return returnDictionary
        }
    }
    

    // call merge with our own custom merging logic when a given key exists in both dictionaries
    let result = dictA.merge(dictB) {$0.list.appendContentsOf($1.list)}
    

    虽然这只有在每次调用函数时合并逻辑都发生变化时才真正有意义,但我怀疑这不是你在做的事情。

    如果您希望合并逻辑保持不变,那么最好使用协议来完成。使用闭包,每次要调用函数时都必须传入合并逻辑。使用协议,您只需定义该逻辑一次 - 并在需要时调用它。

    【讨论】:

    • 谢谢!有没有更功能性的编程方法呢?使用闭包?这就是我想要遵循的,而不是基于协议的方法。 (不想开始在我的代码中混合范式......)
    • @Daniel 我已经更新了我的答案,展示了如何做到这一点,尽管 IMO 协议是一种更好的方法。使用闭包,每次调用函数时都必须传入自定义逻辑,这并不理想。协议允许您定义此逻辑一次,并在需要时调用它。
    • 很好的答案,谢谢!一个问题——为什么在函数式编程解决方案中调用merge()时需要做"var returnValue = $0"?
    • @Daniel 乐于助人:) 我们必须做var returnValue = $0 的原因与您必须在原始函数中做var returnDictionary = withDictionary 的原因相同——函数输入是不可变的,所以我们有在我们可以追加到数组之前创建它的可变副本(因为更改值类型的属性是该值类型的突变)。
    • @Daniel 实际上考虑一下,您可以在闭包中使用inout 参数,从而允许您直接进行突变。我已经更新了我的答案,显示了这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-15
    • 2021-12-28
    • 2012-09-12
    • 2013-04-17
    • 1970-01-01
    相关资源
    最近更新 更多