Swift Dictionary Default Value

Swift Dictionary default value

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]

Idiomatic way to construct a default value for a dictionary key in swift and set it

There is no way to set a default value for a Dictionary, but you can specify a default value when looking up a dictionary value:

var arr = myDict["key", default: []]

default provides a default value if the key is not found. In this case, you can use [] which Swift will infer to be an empty array of type [String].

Using an enum's default value as a dictionary key without explicitly casting to String

Update and improvement for Swift 4.2

extension Dictionary {
subscript(key: APIKeys) -> Value? {
get {
guard let key = key.stringValue as? Key else { return nil }
return self[key]
}
set(value) {
guard let key = key.stringValue as? Key else { return }
guard let value = value else { self.removeValue(forKey: key); return }
self.updateValue(value, forKey: key)
}
}
}

protocol APIKeys {}
extension APIKeys {
var stringValue: String {
return String(describing: self)
}
}

enum Keys: APIKeys {
case key_one
case key_two
}

var myStringDict = [AnyHashable : Any]()
var model1StringDict = [String : Any]()
var model2StringDict = [String : String]()

myStringDict.updateValue("firstValue", forKey: Keys.key_one.stringValue) // [key_one: firstValue]
myStringDict[Keys.key_two] = "secondValue" // [key_two: secondValue, key_one: firstValue]
myStringDict[Keys.key_one] = nil // [key_two: secondValue]
myStringDict.removeValue(forKey: Keys.key_two.stringValue) // []

model1StringDict.updateValue("firstValue", forKey: Model1Keys.model_1_key_one.stringValue) // [model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue" // [model_1_key_two: secondValue, model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_one] = nil // [model_1_key_two: secondValue]

model2StringDict.updateValue("firstValue", forKey: Model2Keys.model_2_key_one.stringValue) // [model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue" // [model_2_key_two: secondValue, model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_one] = nil // [model_2_key_two: secondValue]

I specifically changed the types of the 3 dictionaries to show the common ways of typing a dictionary ([AnyHashable : Any], [String : Any], and [String : String]), and showed that this works with each of the types.

It's important to note that if you use updateValue instead of the assignment operator when the key of your dictionary is AnyHashable, then you need to specify the String value of the key with .stringValue. Otherwise, the exact type of the key being stored will not explicitly be a String, and it'll get messed up later if you try to, say, remove a value under your key via assigning nil to that key. For a dictionary where the key is specifically typed to be String, then the updateValue function will have a compile time error saying that the key needs to be a String, so you can't mess it up that way.

Swift 2.3

I figured out the extension solution that I wanted.

extension Dictionary {

subscript(key: Keys) -> Value? {
get {
return self[String(key) as! Key]
}
set(value) {
guard
let value = value else {
self.removeValueForKey(String(key) as! Key)
return
}

self.updateValue(value, forKey: String(key) as! Key)
}
}

}

enum Keys {
case key_one
case key_two
}

var myStringDict = [String : String]()
/// Adding the first key value through the String() way on purpose
myStringDict.updateValue("firstValue", forKey: String(Keys.key_one))
// myStringDict: ["key_one": "firstValue"]
// myStringDict[Keys.key_one]!: firstValue

myStringDict[Keys.key_two] = "secondValue"
// myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]

myStringDict[Keys.key_one] = nil
// myStringDict: ["key_two": "secondValue"]

Notice that the declared dictionary key type is String, but I'm able to just use Keys.key_one and the subscript in the dictionary extension takes care of the rest.

I can probably put some better guarding around the as! conversion to Key, but I'm not sure it's needed, as I know that my enum can always be converted to a valid Key by the String() cast.

Improvement to answer

Even better, since I'm using this for API Keys, I made a blank protocol called APIKeys and each model will implement their own Keys enum that conforms to the APIKeys protocol. And the dictionary's subscript is updated to take in APIKeys as the Key value.

extension Dictionary {

subscript(key: APIKeys) -> Value? {
get {
return self[String(key) as! Key]
}
set(value) {
guard
let value = value else {
self.removeValueForKey(String(key) as! Key)
return
}

self.updateValue(value, forKey: String(key) as! Key)
}
}

}

protocol APIKeys {}

enum Keys: APIKeys {
case key_one
case key_two
}

enum Model1Keys: APIKeys {
case model_1_key_one
case model_1_key_two
}

enum Model2Keys: APIKeys {
case model_2_key_one
case model_2_key_two
}

var myStringDict = [String : String]()
var model1StringDict = [String : String]()
var model2StringDict = [String : String]()

myStringDict.updateValue("firstValue", forKey: String(Keys.key_one)) // myStringDict: ["key_one": "firstValue"]
myStringDict[Keys.key_two] = "secondValue" // myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]
myStringDict[Keys.key_one] = nil // myStringDict: ["key_two": "secondValue"]

model1StringDict.updateValue("firstValue", forKey: String(Model1Keys.model_1_key_one)) // model1StringDict: ["model_1_key_one": "firstValue"]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue" // model1StringDict: ["model_1_key_one": "firstValue", "model_1_key_two": "secondValue"]
model1StringDict[Model1Keys.model_1_key_one] = nil // model1StringDict: ["model_1_key_two": "secondValue"]

model2StringDict.updateValue("firstValue", forKey: String(Model2Keys.model_2_key_one)) // model2StringDict: ["model_2_key_one": "firstValue"]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue" // model2StringDict: ["model_2_key_one": "firstValue", "model_2_key_two": "secondValue"]
model2StringDict[Model2Keys.model_2_key_one] = nil // model2StringDict: ["model_2_key_two": "secondValue"]

Most optimal way to increment or initialize an Int in a Swift Dictionary

Update

Since Swift 4 you can use a default value in the subscript (assuming the dictionary has Value == Int):

dict[key, default: 0] += 1

You can read more about it in the Swift Evolution proposal Dictionary & Set Enhancements under Key-based subscript with default value


One way would be to use the nil coalescing operator:

dict[key] = ((dict[key] as? Int) ?? 0) + 1

If you know the type of the dictionary you can almost do the same but without casting:

dict[key] = (dict[key] ?? 0) + 1

Explanation:

This operator (??) takes an optional on the left side and a non optional on the right side and returns the value of the optional if it is not nil. Otherwise it returns the right value as default one.

Like this ternary operator expression:

dict[key] ?? 0
// is equivalent to
dict[key] != nil ? dict[key]! : 0

How to set the default value of a Date in Swift?

You can set current date of device with giving Date() which gives you a current date of device

Does the Swift standard Dictionary have a get-or-set function?

No it doesn't have a function like this. It is fine to create an extension in this circumstance.

Swift 3 equivalent of setting a value to a dictionary key if the key doesn't exist

The Nil-Coalescing Operator

foo["someArg"] = foo["someArg"] ?? "default value"

The nil-coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil.

If copy-on-write performance is a concern, just use an if statement:

if foo["someArg"] == nil { foo["someArg"] = "default value" }

Swift 4 Key Value Dictionary stored in User Defaults

There is a major mistake. You save a dictionary but retrieve an array.

Apart from that a dictionary retrieved from UserDefaults is [String:Any] by default, you have to conditional downcast the object.

The code checks if there is a dictionary in UserDefaults and if there is the requested key in one expression

public func checkDatabaseMatch( _ name: String, _ number: String) -> Bool
{
guard let database = UserDefaults.standard.dictionary(forKey: "Database") as? [String:String],
let databaseCheck = database[name] else { return false }

return databaseCheck == number
}

Another mistake is that you are always overwriting the entire dictionary in UserDefaults. If you want to save multiple key-value pairs you have to read the dictionary first.

public func saveToDatabase( _ name: String, _ number: String)
{
var newEntry : [String: String]
if let database = UserDefaults.standard.dictionary(forKey: "Database") as? [String:String] {
newEntry = database
} else {
newEntry = [:]
}
newEntry[name] = number

UserDefaults.standard.set(newEntry, forKey: "Database")
}

Side note: The parameter labels are highly recommended in Swift for better readability.



Related Topics



Leave a reply



Submit