【问题标题】:Swift Dictionary default valueSwift 字典默认值
【发布时间】:2015-09-05 16:16:32
【问题描述】:

我已经习惯使用 Python 的 defaultdicts 的一个模式是一个字典,如果给定键的值没有被显式设置,它会返回一个默认值。在 Swift 中尝试这样做有点冗长。

var dict = Dictionary<String, Array<Int>>()
let key = "foo"
var value: Array<Int>! = dict[key]
if value == nil {
    value = Array<Int>()
    dict[key] = value
}

我意识到我可以创建一个执行此操作的类,但是必须通过属性访问实际的 Dictionary 才能使用任何其他常规 Dictionary 方法

class DefaultDictionary<A: Hashable, B> {
    let defaultFunc: () -> B
    var dict = Dictionary<A, B>()

    init(defaultFunc: () -> B) {
        self.defaultFunc = defaultFunc
    }
    subscript(key: A) -> B {
        get {
            var value: B! = dict[key]
            if value == nil {
                value = defaultFunc()
                dict[key] = value
            }
            return value
        }
        set {
            dict[key] = newValue
        }
    }
}

有更好的模式吗?

【问题讨论】:

  • 你可以使用 nil-coalescing 操作符??,像这样:var value: Array&lt;Int&gt; = dict[key] ?? []
  • @TheParamagneticCroissant 这将为变量分配所需的值,但不会更新字典
  • 然后写dict[key] = dict[key] ?? [],然后你可以强制解开dict[key]
  • 你能不能把DefaultDictionary变成Dictionary的子类并继承超类的所有方法?
  • @martineau:字典是一个结构,不能被子类化。

标签: swift dictionary


【解决方案1】:

这在 Swift 4 中发生了变化,现在有一种方法可以读取键的值或在键不存在时提供默认值。例如:

let person = ["name": "Taylor", "city": "Nashville"]
let name = person["name", default: "Anonymous"]

这在修改字典值时特别有用,因为您可以编写如下代码:

var favoriteTVShows = ["Red Dwarf", "Blackadder", "Fawlty Towers", "Red Dwarf"]
var favoriteCounts = [String: Int]()

for show in favoriteTVShows {
    favoriteCounts[show, default: 0] += 1
}

我在文章 What's new in Swift 4 中介绍了此更改和其他更改。

【讨论】:

  • 很高兴知道,但它并不能完全完成同样的事情。使用类似于 defaultdict 的方法,您可以声明一次默认值,这很好。使用这种方法,看起来您每次从字典中读取时都必须传递默认值。
  • @alexp ,虽然看起来您每次都必须传递默认值,但 default 的参数被声明为 @autoclosure,它围绕作为默认参数,从而确保仅在密钥不存在时才对其进行评估/传递。所以它更像是 defaultdict 而不是一开始意识到的。
  • 自动关闭很好,但真正的问题是字典不保留默认值。它在所示示例中有效,因为它也使用+= 设置值,但在其他情况下它可能完全无效。
【解决方案2】:

使用 Swift 2,您可以实现类似于 python 的版本,扩展名为Dictionary

// Values which can provide a default instance
protocol Initializable {
    init()
}

extension Dictionary where Value: Initializable {
    // using key as external name to make it unambiguous from the standard subscript
    subscript(key key: Key) -> Value {
        mutating get { return self[key, or: Value()] }
        set { self[key] = newValue }
    }
}

// this can also be used in Swift 1.x
extension Dictionary {
    subscript(key: Key, or def: Value) -> Value {
        mutating get {
            return self[key] ?? {
                // assign default value if self[key] is nil
                self[key] = def
                return def
            }()
        }
        set { self[key] = newValue }
    }
}

?? 之后的闭包用于类,因为它们不传播其值突变(仅“指针突变”;引用类型)。

字典必须是可变的 (var) 才能使用这些下标:

// Make Int Initializable. Int() == 0
extension Int: Initializable {}

var dict = [Int: Int]()
dict[1, or: 0]++
dict[key: 2]++

// if Value is not Initializable
var dict = [Int: Double]()
dict[1, or: 0.0]

【讨论】:

    【解决方案3】:

    除非我误解了 Python 中的 defaultdict,否则我看不出 nil 合并对你不起作用。假设您有一个 [Int:Int] 类型的字典,并且您希望它默认返回 0。使用 nil 合并它看起来像这样:

    let dict = [1:10, 2:8, 3:64]
    let valueForKey = dict[4] ?? 0
    

    您在评论中提到这不起作用,因为它不会更新字典。不过,我不明白这个问题:如果您知道 nil 的每个实例都将被您的默认值替换,为什么还需要更新字典?也许我在这里遗漏了一些东西,但似乎默认值和 nil 合并(实际上)是相同的。

    You can change the syntax a little, if it makes things more clear:

    extension Dictionary {
      subscript(key: Key, or r: Value) -> Value {
        get { return self[key] ?? r }
        set { self[key] = newValue }
      }
    }
    

    在这种情况下,上面的例子可以这样写:

    let dict = [1:10, 2:8, 3:64]
    let valueForKey = dict[4, or: 0]
    

    在这种情况下,变异方法可以作用于键,如下所示:

    var dict = [2: 8, 3: 64, 1: 10]
    dict[2, or: 0]++
    dict // [2: 9, 3: 64, 1: 10]
    dict[4, or: 0]++
    dict // [2: 9, 3: 64, 1: 10, 4: 1]
    

    【讨论】:

    • 如果Valueclass 类型则不起作用,因为引用类型不会将其突变传播给所有者。
    • 是的,这基本上是一个语法问题。在 python 中,我可以说 x = collections.defaultdict(list) 然后执行 x["foo"].append("bar") 而不关心 x["foo"] 是否已经初始化,这很好。每次都必须重复默认值不是很方便。
    • 哦,好的。那讲得通。是的,不幸的是,那么可能没有简单的方法来做你正在寻找的东西。上面的方法大概是最“Swifty”的了。
    【解决方案4】:

    这个扩展类似于 Swift 4 中的default 下标,不同之处在于它实际上将默认值存储在字典中。 (这也类似于 QByte 的答案,不同之处在于它使用自动关闭来防止在不需要时访问默认值)。

    extension Dictionary {
        subscript(key: Key, setDefault defaultValue: @autoclosure () -> Value) -> Value {
            mutating get {
                return self[key] ?? {
                    let value = defaultValue()
                    self[key] = value
                    return value
                }()
            }
        }
    }
    

    请注意,没有为下标定义 setter,因为标准 default 下标已经实现了这个目的。

    例子:

    var items = [String: ComplexItem]()
    
    let item1 = items["milk", setDefault: ComplexItem()]
    let item2 = items["milk", setDefault: ComplexItem()]
    

    这里ComplexItem 只创建一次,因为字典在第一次访问后保留了它。

    【讨论】:

      猜你喜欢
      • 2019-08-17
      • 2011-06-29
      • 1970-01-01
      • 1970-01-01
      • 2021-07-27
      • 2017-06-10
      • 1970-01-01
      • 2015-05-07
      • 2011-05-04
      相关资源
      最近更新 更多