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

Swift 将[String:String]转换为[String:URL]并展平nil值,swift,dictionary,Swift,Dictionary,假设我有一个[String:String]类型的字典,我想将其转换为[String:URL]类型。我可以使用map或flatMap转换字典,但由于URL(字符串:)初始值设定项存在故障,因此我的值是可选的: let source = ["google" : "http://google.com", "twitter" : "http://twitter.com"] let result = source.flatMap { ($0, URL(string: $1)) } 这将返回类型为[(字

假设我有一个
[String:String]
类型的字典,我想将其转换为
[String:URL]
类型。我可以使用
map
flatMap
转换字典,但由于
URL(字符串:)
初始值设定项存在故障,因此我的值是可选的:

let source = ["google" : "http://google.com", "twitter" : "http://twitter.com"]

let result = source.flatMap { ($0, URL(string: $1)) }
这将返回类型为
[(字符串,URL?)
的值,而不是
[字符串:URL]
。是否只有一行代码可以用单个方法转换此词典?我的第一个想法是:

source.filter { $1 != nil }.flatMap { ($0, URL(string: $1)!) }
但是我不需要检查值是否为
nil
values
在字典具体值上永远不会返回
nil
),我需要检查
URL(字符串:)
的返回值是否为
nil

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

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

以下是一些解决方案:

//: 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)
/:操场-名词:人们可以玩耍的地方
进口基金会
让source=[“谷歌”:http://google.com“,”推特“:”http://twitter.com“,”坏“:”]
//:第一个解决方案利用了一个事实,即flatMap、map和filter都可以在reduce方面实现。
扩展字典{
///更新的不可变版本。返回包含self值和传入的键/值的新字典。
func updatedValue(value:value,forKey:key)->字典{
var结果=自身
结果[键]=值
返回结果
}
}
让result=source.reduce([String:URL]()){result,中的项
guard let url=url(字符串:item.value)else{return result}
返回result.updatedValue(url,forKey:item.key)
}
打印(结果)
//:此解决方案使用使用键/值元组的自定义字典初始值设定项。
扩展字典{
//从键/值对数组构造字典。
初始(项:[(键:键,值:值)]){
self.init()
对于项目中的项目{
self[item.key]=item.value
}
}
}
让项目=源
.map{($0,URL(字符串:$1))}//将值转换为URL?s
.filter{$1!=nil}//筛选出未转换的
.map{($0,$1!)}//强制打开已打开的。
let result2=字典(项:项)
打印(结果2)
//:此解决方案还使用上述初始值设定项。由于展开可选值可能是一件常见的事情,所以此解决方案提供了一种处理展开的方法。
协议可选类型{
关联类型包装
var asOptional:已包装?{get}
}
扩展名可选:OptionalType{
var可选:包装{
回归自我
}
}
扩展字典,其中值:OptionalType{
//将[键:可选]展平到[键:类型]
func flattValues()->字典{
让items=self.filter{$1.asOptional!=nil}.map{($0,$1.asOptional!))
返回字典(项目:项目)
}
}
让result3=Dictionary(items:source.map{($0,URL(字符串:$1))})
打印(结果3)

您需要确保返回的元组仅包含非可选值,并且由于可选值本身支持
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 }
但是,既然我们已经扩展了字典创建(我认为没有内置的方法从数组创建dict),那么您不妨这样做:

var dict = [String: URL]()
source.forEach { if let u = URL(string: $1) { dict[$0] = u } }

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))) } )
let source=[“谷歌”:http://google.com“,”推特“:”http://twitter.com“,”失败“:“]
//来自(键、值)元组数组的字典。这真的应该建造吗
扩展字典{
公共init(u数组:[元素]){
self.init()
array.forEach{self[$0.key]=$0.value}
}
}
//将可选元组转换为可选元组。Note将强制使用非可选字符,以便在(A,B?)或(A?,B)上使用2,3,4元组的变体。
func-raiseOptionality(uu-tuple:(A?,B?)->(A,B)?{
守卫let a=tuple.0,let b=tuple.1 else{return nil}
返回(a、b)
}
let result=Dictionary(source.flatMap{raiseOptionality($0,URL(字符串:$1)))}

若你们只想用一个好的、已知的URL来代替坏的URL,那个就简单多了

使用


声明您的空结果
var result:[String:URL]=[:]
然后使用forEach将键值添加到它
source.forEach{guard let value=URL(String:$0.value)或者{return}result[$0.key]=value}
这样您只需在eafaik上迭代,没有返回字典的标准库映射函数(真正的问题是)-您必须实现自己的,或者只使用可靠的旧for循环。@LeoDabus a
nil
value意味着给定键不存在它,因此您完全可以说
source.forEach{result[$0.key]=URL(string:$0.value)}
。谢谢你们两位。我担心当前唯一的方法是使用循环和空结果。相关:这是正确的,但仍然需要使用
for
循环初始化和引用外部字典(如注释中讨论的)。我想Swift目前不支持使用
map
的纯解决方案。@JAL因为你不能将数组传递给字典构造函数,没有扩展方法,你就不能做得更好了。好消息是,文字实现的代码更少,可以说比任何函数方法都更可读。:)不确定你是否读过我的问题,我不想提供一个默认值,我想把nil值展平

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