2015-10-30 4 views
11

Ich habe diese Funktion:in Verlängerung

func flatten<Key: Hashable, Value>(dict: Dictionary<Key, Optional<Value>>) -> Dictionary<Key, Value> { 
    var result = [Key: Value]() 
    for (key, value) in dict { 
     guard let value = value else { continue } 
     result[key] = value 
    } 
    return result 
} 

Wie Sie sehen können, es wandelt ein [Key: Value?] Wörterbuch in ein [Key: Value] ein (ohne die optional).

Ich wollte die Dictionary Klasse mit einer neuen Methode nur für Klassen, deren Wert ist ein Optional von einem beliebigen Typ, aber ich bin nicht in der Lage, Einschränkungen zu den generischen Parameter des Wörterbuchs hinzuzufügen. Diese

ist, was ich versucht:

extension Dictionary where Value: Optional<Any> { 
    func flatten() -> [Key: Any] { 
     var result = [Key: Any]() 
     for (key, value) in self { 
      guard let value = value else { continue } 
      result[key] = value 
     } 
     return result 
    } 
} 

Aber irgendwie Fehler:

Type 'Value' constrained to non-protocol type 'Optional<Any>' 

Antwort

16

diesen Code Versuchen im Spielplatz:

// make sure only `Optional` conforms to this protocol 
protocol OptionalEquivalent { 
    typealias WrappedValueType 
    func toOptional() -> WrappedValueType? 
} 

extension Optional: OptionalEquivalent { 
    typealias WrappedValueType = Wrapped 

    // just to cast `Optional<Wrapped>` to `Wrapped?` 
    func toOptional() -> WrappedValueType? { 
    return self 
    } 
} 

extension Dictionary where Value: OptionalEquivalent { 
    func flatten() -> Dictionary<Key, Value.WrappedValueType> { 
    var result = Dictionary<Key, Value.WrappedValueType>() 
    for (key, value) in self { 
     guard let value = value.toOptional() else { continue } 
     result[key] = value 
    } 
    return result 
    } 
} 

let a: [String: String?] = ["a": "a", "b": nil, "c": "c", "d": nil] 
a.flatten() //["a": "a", "c": "c"] 

Weil Sie keine genaue Typ angeben in der where Klausel einer Protokollerweiterung, eine Möglichkeit, die Sie genau dieerkennen könnenTyp ist zu machen Optional UNIQUELY entspricht einem Protokoll (sagen OptionalEquivalent).

Um den verpackten Werttyp der Optional zu bekommen, definierte ich einen typealias WrappedValueType im benutzerdefinierten Protokoll OptionalEquivalent und machte dann eine Verlängerung des Wunsches assgin die Wrapped-WrappedValueType, dann können Sie den Typen in der Flatten Methode erhalten .

Beachten Sie, dass die sugarCast Methode nur die Optional<Wrapped> zu Wrapped? werfen (das ist genau das gleiche), die Verwendung guard Aussage zu ermöglichen.

UPDATE

Dank Rob Napier ‚s Kommentar, den ich vereinfacht & umbenannt sugarCast() -Methode und umbenannt das Protokoll es verständlicher zu machen.

+0

Sie können leichter Implementiere 'sugarCast()' als 'return self'. Ich empfehle persönlich, dieses Protokoll 'OptionalConvertible' und' sugarCast() '' toOptional() 'zu nennen, aber Ihr Weg ist auch in Ordnung. –

+0

Sie haben Recht, ich werde meinen Code aktualisieren – cezheng

+0

Das ist ein sehr kreativer Weg zur Umgehung generischer Einschränkungen. Ich werde es wahrscheinlich an mehr Orten verwenden. Danke – redent84

2

Sie können es auf eine einfachere Weise tun. Dies funktioniert mit Swift 4:

extension Dictionary { 
    func flatten<Wrapped>() -> [Key: Wrapped] where Value == Optional<Wrapped> { 
     return filter { $1 != nil }.mapValues { $0! } 
    } 
} 

Wenn Sie die Verwendung von Funktionen höherer Ordnung nicht gefällt oder müssen die Kompatibilität mit früheren Versionen von Swift Sie dies auch tun können:

extension Dictionary { 
    func flatten<Wrapped>() -> [Key: Wrapped] where Value == Optional<Wrapped> { 
     var result: [Key: Wrapped] = [:] 
     for (key, value) in self { 
      guard let value = value else { continue } 
      result[key] = value 
     } 
     return result 
    } 
} 
+1

Mehr einfach und lesbar als die angenommene Antwort – moskis