将[String:String]转换为[String:URL]并展平nil值

时间:2017-02-08 01:04:06

标签: swift dictionary

假设我有一个类型[String : String]的字典,我想将其转换为[String : URL]类型。我可以使用mapflatMap来转换字典,但由于可用的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)!) }

但是,我不需要检查值是nilvalues永远不会在字典具体值上返回nil),我需要检查是否返回URL(string:)的值为nil

我可以使用filter删除nil值,但这并不会更改返回类型:

source.flatMap { ($0, URL(string: $1)) }.filter { $1 != nil }

4 个答案:

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