2016-06-10 15 views
2

Ich möchte eine generische Funktion, um zwei Wörterbücher zusammenzuführen. Es muss mehr Allzweck sein als der, der in How do you add a Dictionary of items into another Dictionary beschrieben ist. Grund: Ich habe Objekte in meinen Wörterbüchern, und ich möchte nur eine bestimmte Eigenschaft im Gegensatz zu dem gesamten Objekt aktualisieren. (Um konkret zu sein, sind sie Objekte mit einer Array-Eigenschaft. Ich muss entweder an dieses Array anhängen, wenn das Objekt existiert, oder ein neues Objekt mit einem neuen Array erstellen. Ich kann das ganze Objekt nicht einfach untersuchen und entsprechend überschreiben. Das interessiert in seiner Eigenschaft)Generische Funktion zum Zusammenführen zweier Wörterbücher

ich habe versucht, dies mit der funktionalen Programmierung zu tun.

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 

    } 

} 

Antwort

3

diese allgemeine Zusammenführung sollte nicht wissen müssen, dass diese besondere Wörterbuch Wertobjekte hat, die eine Liste enthalten ' Eigentum.

Im Gegenteil, für Ihre Funktion, um die list Eigenschaft auf dem Wörterbuch der Werte zugreifen zu können, müssen Sie den Compiler sagen, dass die Werte haben eine list Eigenschaft. Sie können dies tun, indem ein Protokoll und Einschränken der Erweiterung zu schaffen nur auf Wörterbücher zu arbeiten, die Werte haben, die diesem Protokoll entsprechen:

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

Anschließend können Sie die Werttypen einfach anpassen Sie dieses Protokoll verwenden entweder direkt oder durch eine Erweiterung.

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

Wenn Sie einen allgemeineren Ansatz wollen, könnten Sie ein Mergable Protokoll definieren, das ein Verfahren definiert, wo die bestimmungsgemäße Art ihre eigene Verbindungslogik tun. Sie würden dann Ihre Erweiterung ändern, um diese Methode aufzurufen, wenn die Werte dem Protokoll entsprechen, andernfalls können Sie einfach die Schlüssel/Wert-Paare zusammenführen.

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

Sie könnten dann Ihren Wert Typ dieses Protokolls entsprechen in etwa so:

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

Wenn Werte Ihr Wörterbuch der Mergable sind, dann wird der Compiler die Erweiterung favorisieren, die die benutzerdefinierte verschmelzenden Logik der Fall ist, wie die mehr typspezifische Signatur wird bevorzugt.


In Antwort auf Ihren Kommentar zu wollen, dies mit Schließungen zu tun, Sie könnten einen Verschluss Argument die merge Funktion hinzufügen, die eine Pseudoreferenz auf den ersten Wert (durch Verwendung inout) passieren, zusammen mit der zweite Wert, mit dem Sie den ersten Wert beim Aufrufen der Funktion ändern können.

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

Obwohl dies nur dann wirklich sinnvoll, wenn Ihre verschmelzenden Logik ändert sich mit jedem Aufruf der Funktion, die ich vermute, ist nicht das, was du tust.

Wenn Sie möchten, dass Ihre Merging-Logik konstant bleibt, dann ist dies besser mit Protokollen möglich. Bei Closures müssen Sie jedes Mal die Merging-Logik übergeben, wenn Sie die Funktion aufrufen möchten. Bei Protokollen definieren Sie diese Logik nur einmal - und sie wird bei Bedarf aufgerufen.

+0

danke! Gibt es einen funktionaleren Programmieransatz? Schließungen verwenden? Dem möchte ich im Gegensatz zum protokollbasierten Ansatz folgen. (Ich möchte nicht anfangen, die Paradigmen in meinem Code zu mischen ...) – Daniel

+0

@Daniel Ich habe meine Antwort aktualisiert, die zeigt, wie man es so machen kann, obwohl IMO-Protokolle eine bessere Möglichkeit sind, dies zu tun. Wenn Sie Closures verwenden, müssen Sie bei jedem Aufruf der Funktion Ihre benutzerdefinierte Logik übergeben, was nicht ideal ist. Protokolle ermöglichen es Ihnen, diese Logik einmal zu definieren und sie bei Bedarf aufzurufen. – Hamish

+0

Große Antwort, danke! Eine Frage - warum müssen Sie "var returnValue = $ 0" ausführen, wenn Sie merge() in der funktionalen Programmierlösung aufrufen? – Daniel

Verwandte Themen