Valor predeterminado del diccionario Swift

Un patrón al que me he acostumbrado con los elementos predeterminados de Python es un diccionario que devuelve un valor predeterminado si el valor de una clave determinada no se ha establecido explícitamente. Tratar de hacer esto en Swift es un poco detallado.

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

Me doy cuenta de que puedo crear una clase que haga esto, pero luego se debe acceder al Diccionario real a través de una propiedad para usar cualquiera de los otros métodos normales del Diccionario.

 class DefaultDictionary { let defaultFunc: () -> B var dict = Dictionary() 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 } } } 

¿Hay un mejor patrón para esto?

Utilizando Swift 2 puedes lograr algo similar a la versión de python con una extensión de 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 } } } 

El cierre después de la ?? se utiliza para las clases ya que no propagan su mutación de valor (solo “mutación de puntero”; tipos de referencia).

Los diccionarios deben ser mutables ( var ) para poder usar esos subíndices:

 // 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] 

Esto cambió en Swift 4, y ahora hay una manera de leer el valor de una clave o proporcionar un valor predeterminado si la clave no está presente. Por ejemplo:

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

Esto es particularmente útil al modificar los valores del diccionario, porque puede escribir código como este:

 var favoriteTVShows = ["Red Dwarf", "Blackadder", "Fawlty Towers", "Red Dwarf"] var favoriteCounts = [String: Int]() for show in favoriteTVShows { favoriteCounts[show, default: 0] += 1 } 

Cubrí este cambio y otros en mi artículo Novedades en Swift 4 .

A menos que no haya entendido bien el código predeterminado en Python, no veo cómo la fusión nula no funcionaría para ti. Digamos que tenías un diccionario de tipo [Int:Int] , y querías que devolviera 0 por defecto. Con nulo coalescente se ve así:

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

Usted mencionó en un comentario que eso no funcionaría porque no actualizaría el diccionario. Sin embargo, no entiendo el problema: ¿por qué necesitarías actualizar el diccionario si supieras que cada instancia de nil sería reemplazada por tu valor predeterminado? Tal vez me esté perdiendo algo aquí, pero parece que los valores predeterminados y la fusión nula son (en la práctica) lo mismo.

Puedes cambiar un poco la syntax, si se aclaran las cosas :

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

En este caso, el ejemplo anterior podría escribirse así:

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

En este caso, los métodos de mutación pueden funcionar en las claves, como esto:

 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]