2

I am using let to unwrap an optional from dictionary, but find it cumbersome to deal with the else case.

  if let d : Dog = zoo["Barky"]  {
    d.bark()
  } else {
    // Create missing Dog
    let d : Dog = Dog.init()
    zoo["Barky"] = d
    d.bark()
  }

Is there a way to make this more concise / elegant?

  • How to pull out the duplicate call to bark()
  • How to avoid the duplicate let definition.
Christopher Oezbek
  • 23,994
  • 6
  • 61
  • 85
  • Related: [Does the Swift standard Dictionary have a get-or-set function?](https://stackoverflow.com/questions/41001705/does-the-swift-standard-dictionary-have-a-get-or-set-function) – jscs Jun 18 '17 at 21:53

5 Answers5

1

You can use the ?? operator this way:

let d = zoo["Barky"] ?? Dog.init()
zoo["Barky"] = d
d.bark()

In Swift 4 dictionaries can have default values. Example:

let d = zoo["Barky", default: Dog.init()]
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
  • I thought so myself, but unfortunately he still needs to store the new `Dog` in the `zoo` dictionary. – Paulo Mattos Jun 18 '17 at 21:58
  • The only fast way that comes to my mind is to add the object to the dictionary the line after. Unfortunately there aren't multiple assignments in Swift. – Ramy Al Zuhouri Jun 18 '17 at 22:02
  • I don't think that initiating a new dictionary is a good idea, because if the dic is a bit more complicated, it would be harder to use your answer – Hady Nourallah Jun 18 '17 at 22:08
1

What about this?

if zoo["Barky"] == nil {
    zoo["Barky"] = Dog()
}

zoo["Barky"]?.bark()
Matusalem Marques
  • 2,399
  • 2
  • 18
  • 28
0

A somewhat shorter alternative:

var d: Dog!= zoo["Barky"]
if d == nil {
    d = Dog()
    zoo["Barky"] = d
}
d.bark()

or (if you don't mind checking your dictionary twice):

if zoo["Barky"] == nil {
    zoo["Barky"] = Dog()
}
zoo["Barky"]!.bark()
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
0

you can make it in two lines of code. :)

let d: Dog = zoo["Barky"] ?? Dog()
zoo["Barky"] = d 
d.bark()
Hady Nourallah
  • 442
  • 4
  • 12
  • 1
    This doesn't add the new dog to the dictionary though. – Keiwan Jun 18 '17 at 21:57
  • you can add this line `zoo["Barky"] = d ` easily, let me edit my answer – Hady Nourallah Jun 18 '17 at 21:58
  • Now you're unnecessarily inserting `d` into the dictionary even if it's the same value that already exists in the dictionary - and this is going to be the case every time except for the first time. – Keiwan Jun 18 '17 at 22:01
  • tru dat, but if it is the values are same, they share same address, and because of memory management in iOS, this line will be basically ignored. if it was different it is gonna add it to the dictionary which is what is needed. :) – Hady Nourallah Jun 18 '17 at 22:04
  • @HadyNourallah Swift's ARC won't help you here ;) True, no memory leaks will result from this, but I can't see how the `Dictonary` update logic will be able to optimize his way around this. – Paulo Mattos Jun 18 '17 at 22:12
0

Thanks everyone! I found the following snippet in another answer, which I like:

let d = zoo["Barky"] ?? {
  let d = Dog()
  zoo["Barky"] = d
  return d
}()

Since I needed that idiom in quite many place, I also went with an extension to Dictionary:

extension Dictionary {

    mutating func get_or_set(_ key: Key, defaultValue: () -> Value) -> Value {
        if let value = self[key] {
            return value
        } else {
            let value = defaultValue()
            self[key] = value
            return value
        }
    }
}

let d = zoo.get_or_set("Barky", defaultValue: { Dog() })

Then I found that the subscript operator could be overloaded as well:

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

var d = zoo["Barky", defaultFunc: { Dog.init() }]
Christopher Oezbek
  • 23,994
  • 6
  • 61
  • 85