【问题标题】:Elegantly populate dictionary from a struct checking nil values从检查 nil 值的结构中优雅地填充字典
【发布时间】:2017-11-09 22:24:00
【问题描述】:

给定一个结构A,我想用该结构中的值填充NSDictionary,前提是它们不为零。
为此,我插入所有值,然后遍历字典,删除所有 nil。有没有更优雅、更简洁、更少暴力的解决方案?

struct A {
    var first:String?
    var second:String?
    var third:String?

    /* some init */
}

let a = A(/*some init*/)

var dictionary = [
    "first":a.first,
    "second":a.second,
    "third":a.third
]

for (key,value) in dictionary {
    if value == nil {
        dictionary.removeValue(forKey: key)
    }
}

【问题讨论】:

    标签: swift dictionary


    【解决方案1】:

    拥有一个可选值的字典通常不是一个好主意。字典使用nil 的分配作为您要从字典中删除键/值对的指示。此外,字典查找返回一个可选值,所以如果你的值是可选的,你最终会得到一个需要解包两次的双可选。

    您可以使用分配nil 删除字典条目这一事实,通过仅分配值来构建[String : String] 字典。 nil 不会进入字典,因此您不必删除它们:

    struct A {
        var first: String?
        var second: String?
        var third: String?
    }
    
    let a = A(first: "one", second: nil, third: "three")
    
    let pairs: [(String, String?)] = [
        ("first", a.first),
        ("second", a.second),
        ("third", a.third)
    ]
    
    var dictionary = [String : String]()
    
    for (key, value) in pairs {
        dictionary[key] = value
    }
    
    print(dictionary)
    
    ["third": "three", "first": "one"]
    

    正如@Hamish 在 cmets 中指出的那样,您可以为 pairs 使用 DictionaryLiteral(内部只是一个元组数组),这样您就可以使用更简洁的字典语法:

    let pairs: DictionaryLiteral<String,String?> = [
        "first":  a.first,
        "second": a.second,
        "third":  a.third
    ]
    

    所有其他代码保持不变。

    注意:您可以只写DictionaryLiteral 并让编译器推断类型,但我看到 Swift 无法编译或编译大型字典文字的速度非常慢。这就是为什么我在这里展示了显式类型的使用。


    或者,您可以跳过pairsArrayDictionaryLiteral,直接分配值:

    struct A {
        var first: String?
        var second: String?
        var third: String?
    }
    
    let a = A(first: "one", second: nil, third: "three")
    
    var dictionary = [String : String]()
    
    dictionary["first"] = a.first
    dictionary["second"] = a.second
    dictionary["third"] = a.third
    
    print(dictionary)
    
    ["third": "three", "first": "one"]
    

    【讨论】:

    • 您也可以使用DictionaryLiteral 代替[(String, String?)] - 例如let pairs: DictionaryLiteral = ["first" : a.first, "second" : a.second, "third" : a.third]
    • @Hamish,你可以这样做。由于我只是将它们作为对访问,因此将其设置为元组对数组似乎更简单。另外,我发现如果您让 Swift 推断大型字典文字的类型,它可能会很慢或无法编译。我认为,如果您要在这里使用字典,则应将其显式键入为 [String : String?] 以节省编译器的工作量。
    • 您可以显式注释 DictionaryLiteralDictionaryLiteral&lt;String, String?&gt; 的类型,这应该“节省编译器的工作量”:) 值得注意的是,DictionaryLiteral 只不过是一个数组引擎盖下的元组,我建议它的唯一原因是为了稍微改进语法(使用字典文字的能力)。
    • @Hamish。谢谢,DictionaryLiteral 很有趣。它允许重复键并且是有序的。
    【解决方案2】:

    作为@vacawama says,您通常应该避开具有可选值的Dictionary 类型,因为Dictionary API 是围绕使用nil 构建的,以指示给定键缺少值。因此,为 DictionaryValue 使用可选类型会导致混淆双包装可选,这通常会表现出不直观的行为。

    另一个(更过分的)解决方案是通过定义自己的“值展开”ExpressibleByDictionaryLiteral 类型来直接利用字典文字。

    extension Dictionary {
    
        /// A dictionary wrapper that unwraps optional values in the dictionary literal
        /// that it's created with.
        struct ValueUnwrappingLiteral : ExpressibleByDictionaryLiteral {
    
            typealias WrappedValue = Dictionary.Value
    
            fileprivate let base: [Key : WrappedValue]
    
            init(dictionaryLiteral elements: (Key, WrappedValue?)...) {
    
                var base = [Key : WrappedValue]()
    
                // iterate through the literal's elements, unwrapping the values,
                // and updating the base dictionary with them.
                for case let (key, value?) in elements {
    
                    if base.updateValue(value, forKey: key) != nil {
                        // duplicate keys are not permitted.
                        fatalError("Dictionary literal may not contain duplicate keys")
                    }
                }
    
                self.base = base
            }
        }
    
        init(unwrappingValues literal: ValueUnwrappingLiteral) {
            self = literal.base // simply get the base of the unwrapping literal.
        }
    }
    

    然后您可以简单地将这种类型的实例传递给我们的 init(unwrappingValues:) 初始化程序:

    struct A {
        var first: String?
        var second: String?
        var third: String?
    }
    
    let a = A(first: "foo", second: nil, third: "baz")
    
    let dictionary = Dictionary(unwrappingValues: [
        "first"  : a.first,
        "second" : a.second,
        "third"  : a.third
    ])
    
    print(dictionary) // ["third": "baz", "first": "foo"]
    

    【讨论】:

      猜你喜欢
      • 2017-10-04
      • 1970-01-01
      • 2019-06-27
      • 2011-10-22
      • 1970-01-01
      • 2019-11-24
      • 2016-03-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多