假设我有一个类型[String : String]
的字典,我想将其转换为[String : URL]
类型。我可以使用map
或flatMap
来转换字典,但由于可用的URL(string:)
初始化程序,我的值是可选的:
let source = ["google" : "http://google.com", "twitter" : "http://twitter.com"]
let result = source.flatMap { ($0, URL(string: $1)) }
这将返回[(String, URL?)]
类型的值,而不是[String : URL]
。是否有一个单行使用单一方法转换这个字典?我的第一个想法是:
source.filter { $1 != nil }.flatMap { ($0, URL(string: $1)!) }
但是,我不需要检查值是nil
(values
永远不会在字典具体值上返回nil
),我需要检查是否返回URL(string:)
的值为nil
。
我可以使用filter
删除nil
值,但这并不会更改返回类型:
source.flatMap { ($0, URL(string: $1)) }.filter { $1 != nil }
答案 0 :(得分:1)
您需要确保仅使用非可选值返回元组,并且因为可选值本身支持flatMap
,您可以使用它来使元组可选,而不是内部的单个值。它:
let source = [
"google": "http://google.com",
"twitter": "http://twitter.com",
"bad": "",
]
var dict = [String: URL]()
source.flatMap { k, v in URL(string: v).flatMap { (k, $0) } }.forEach { dict[$0.0] = $0.1 }
但是,既然我们已经扩展了字典创建(我不认为这是一种从数组中创建字典的内置方法),你可以这样做:< / p>
var dict = [String: URL]()
source.forEach { if let u = URL(string: $1) { dict[$0] = u } }
答案 1 :(得分:0)
以下是一些解决方案:
//: Playground - noun: a place where people can play
import Foundation
let source = ["google": "http://google.com", "twitter": "http://twitter.com", "bad": ""]
//: The first solution takes advantage of the fact that flatMap, map and filter can all be implemented in terms of reduce.
extension Dictionary {
/// An immutable version of update. Returns a new dictionary containing self's values and the key/value passed in.
func updatedValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> {
var result = self
result[key] = value
return result
}
}
let result = source.reduce([String: URL]()) { result, item in
guard let url = URL(string: item.value) else { return result }
return result.updatedValue(url, forKey: item.key)
}
print(result)
//: This soultion uses a custom Dictionary initializer that consums the Key/Value tuple.
extension Dictionary {
// construct a dictionary from an array of key/value pairs.
init(items: [(key: Key, value: Value)]) {
self.init()
for item in items {
self[item.key] = item.value
}
}
}
let items = source
.map { ($0, URL(string: $1)) } // convert the values into URL?s
.filter { $1 != nil } // filter out the ones that didn't convert
.map { ($0, $1!) } // force unwrap the ones that did.
let result2 = Dictionary(items: items)
print(result2)
//: This solution also uses the above initializer. Since unwrapping optional values is likely a common thing to do, this solution provides a method that takes care of the unwrapping.
protocol OptionalType {
associatedtype Wrapped
var asOptional : Wrapped? { get }
}
extension Optional : OptionalType {
var asOptional : Wrapped? {
return self
}
}
extension Dictionary where Value: OptionalType {
// Flatten [Key: Optional<Type>] to [Key: Type]
func flattenValues() -> Dictionary<Key, Value.Wrapped> {
let items = self.filter { $1.asOptional != nil }.map { ($0, $1.asOptional!) }
return Dictionary<Key, Value.Wrapped>(items: items)
}
}
let result3 = Dictionary(items: source.map { ($0, URL(string: $1)) }).flattenValues()
print(result3)
答案 2 :(得分:0)
Daniel T的最后一个解决方案非常好,如果你想用更实用的风格来编写它。我做的有点不同,主要区别是将选项元组转换为可选元组的方法。我发现这是一个普遍有用的变换,特别是与flatMap结合使用。
let source = ["google" : "http://google.com", "twitter" : "http://twitter.com", "fail" : ""]
// Dictionary from array of (key, value) tuples. This really ought to be built it
extension Dictionary {
public init(_ array: [Element]) {
self.init()
array.forEach { self[$0.key] = $0.value }
}
}
//Turn a tuple of optionals into an optional tuple. Note will coerce non-optionals so works on (A, B?) or (A?, B) Usefull to have variants for 2,3,4 tuples.
func raiseOptionality<A,B>(_ tuple:(A?, B?)) -> (A, B)? {
guard let a = tuple.0, let b = tuple.1 else { return nil }
return (a,b)
}
let result = Dictionary(source.flatMap { raiseOptionality(($0, URL(string: $1))) } )
答案 3 :(得分:0)
如果您只想要一个好的,已知的URL代替坏的URL,那就很容易了。
使用
let source = ["google" : "http://google.com", "twitter" : "http://twitter.com", "bad": ""]
let defaultURL = URL(string: "http://www.google.com")! // or whatever you want for your default URL
let result = source.flatMap { ($0, URL(string: $1) ?? defaultURL) }