合并两个词典的通用函数

时间:2016-06-10 06:09:50

标签: swift dictionary merge functional-programming

我想要一个通用函数来合并两个字典。它需要比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

    }

}

1 个答案:

答案 0 :(得分:3)

  

这个泛型合并不应该知道这个特定的字典有包含'list'属性的值对象。

相反,为了让您的函数访问字典值的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函数添加一个闭包参数,该参数将伪引用传递给第一个值(通过使用inout),以及第二个值,允许您在调用函数时改变第一个值。

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

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

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