Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Dictionary default value

A pattern I've gotten used to with Python's defaultdicts is a dictionary that returns a default value if the value for a given key has not been explicitly set. Trying to do this in Swift is a little verbose.

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

I realize I can make a class that does this, but then the actual Dictionary has to be accessed through a property to use any of the other normal Dictionary methods

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

Is there a better pattern for this?

like image 239
alexp Avatar asked Sep 05 '15 16:09

alexp


People also ask

What is dictionary in Swift?

In swift, a dictionary is a collection of key-value pairs. We can use any type of data as a key and any type of data as a value. We can create a dictionary using the (key:value) syntax. A key should always be a single item. But a value can be a single item or a collection of items, like an array or a dictionary.

How do you check if a dictionary contains a key in Swift?

The contains() method returns: true - if the dictionary contains the specified key or value. false - if the dictionary doesn't contain the specified key or value.


2 Answers

This changed in Swift 4, and there's now a way to read a key's value or provide a default value if the key isn't present. For example:

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

This is particularly useful when modifying dictionary values, because you can write code like this:

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

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

I covered this change and others in my article What's new in Swift 4.

like image 199
TwoStraws Avatar answered Sep 22 '22 23:09

TwoStraws


Using Swift 2 you can achieve something similar to python's version with an extension of 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 }
    }
}

The closure after the ?? is used for classes since they don't propagate their value mutation (only "pointer mutation"; reference types).

The dictionaries have to be mutable (var) in order to use those subscripts:

// 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]
like image 34
Qbyte Avatar answered Sep 22 '22 23:09

Qbyte