12

I have this function:

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
}

As you can see, it transforms a [Key: Value?] dictionary into a [Key: Value] one (without the optional).

I wanted to extend the Dictionary class with a new method only for classes which value is an Optional of any type, but I am unable to add constraints to the generic parameters of the dictionary.

This is what I tried:

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

But fails with error:

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

2 回答 2

16

在 Playground 中尝试以下代码:

// 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"]

因为您不能在where协议扩展的子句中指定确切的类型,所以您可以准确检测类型的一种方法Optional是使OptionalUNIQUELY 符合协议(例如OptionalEquivalent)。

为了得到 的封装值类型Optional,我WrappedValueType在自定义协议中定义了一个 typealias OptionalEquivalent,然后对 Optional 做了一个扩展,赋值WrappedWrappedValueType,然后就可以在 flatten 方法中获取类型了。

请注意,该sugarCast方法只是将Optional<Wrapped>to 转换为Wrapped?(完全相同),以启用使用guard语句。

更新

感谢 Rob Napier 的评论,我已经简化并重命名了 sugarCast() 方法并重命名了协议以使其更易于理解。

于 2015-10-30T13:16:20.660 回答
8

你可以用更简单的方法来做。这适用于 Swift 4:

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

如果您不喜欢使用高阶函数或需要与以前版本的 Swift 兼容,您也可以这样做:

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
    }
}
于 2017-12-15T15:44:53.290 回答