4

this is probably a very basic/stupid question: how do I get a reference to a collection in Swift, such that a change to that reference affects the original and vice versa? So if for instance I have the following code:

var a1 = [Int]()
var a2 = a1
a1.append(1)
print(a2)

Can I get a "reference" (or whatever name it would have in Swift) to a1 such that when I change a1, a2 reflects the same change, and it ends up displaying "[1]" instead of "[]"?

I guess this has to do with collections being primary types, and thus not behaving like other objects, but then I'm at a loss as to how I can play around with collections without them being duplicated all the time.

More specifically, when working with a Dictionary<String, Dictionary<String, Int>>, what's the best way to update the contents of the nested Dictionary while minimizing the number of lookups? The following approach used to work in Java but I guess it's different with Swift:

var dict = Dictionary<String, Dictionary<String, Int>>()
var d = dict["a"]
if d == nil {
    d = Dictionary()
    dict["a"] = d
}
d!["b"] = 1
print("\(dict["a"]!["b"])")

prints "nil"

(Note: if possible, I'd like to avoid multiple dict["a"]!["b"] lookups)

Thanks!

David
  • 437
  • 1
  • 4
  • 15

3 Answers3

3

Value Types

Array, Dictionary and Set (among many others) in Swift are structs. A struct in Swift is a value type so when you assign a value to another variable you create a copy (at least at high level until a real change is done).

This is particularly visibile when you pass a struct to a function

func changeIt(numbers:[Int]) {
    var numbers = numbers
    numbers.removeFirst()
    print("Inside the function \(numbers)") // "Inside function 2, 3"
}


var numbers = [1, 2, 3]
changeIt(numbers)
print("Outside the function \(numbers)") // "Outside the function 1, 2, 3"

Passing a value type reference to a function

You can define a function to receive a reference to a value type adding the keyword inout before the param name

func changeIt(inout numbers:[Int]) {
    numbers.removeFirst()
    print("Inside the function \(numbers)") // Inside the function [2, 3]
}

next when you invoke the function you add & before the param to clarify you are passing a reference to it

var numbers = [1, 2, 3]
changeIt(&numbers)
print("Outside the function \(numbers)") // Outside the function [2, 3]

Your snippet

This theory is not directly related to your code snipped. You can simply build the dictionary starting from the deepest elements like this

var dict = [String : [String : Int]]()

let b: Int = (dict["a"]?["b"]) ?? 1
let a: [String : Int] = dict["a"] ?? ["a": b]
dict["a"] = a

As you can see I am building b and then a using the value in the dictionary (if present) or a default value.

JSON

Finally it looks like you are trying to build a JSON, right? In this case I really suggest you to use SwiftyJSON, it will make things much much easier.

Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • Thanks for the explanation -- makes perfect sense if the collection types are `struct`s. Any idea as to what motivated that choice though? Seems to me like this adds overhead to manipulating collections because of the copying around (even if it's only high level), and also makes the handling of nested collections rather cumbersome... – David Jun 17 '16 at 16:35
  • @David: For many reasons structs are better then classes in several scenarios. Look [here](http://stackoverflow.com/questions/24232799/why-choose-struct-over-class/24232845). – Luca Angeletti Jun 17 '16 at 16:37
  • Also, your code snippet (under "Your snippet") doesn't work. The second line `var b: ...` doesn't compile nor is it required. Without it, the mutation works fine but it still requires more lookups than should be necessary IMHO. Maybe I shouldn't worry so much about hash lookups but when working with large quantities of data (e.g. building a lexicon) it seems to me like one should try and reduce these things, because hash functions are efficient but they're not free. – David Jun 17 '16 at 16:38
  • @David: oops... I'll fix it ASAP! – Luca Angeletti Jun 17 '16 at 16:45
  • @David: I just updated my code snippet, let me know if it does work for you. – Luca Angeletti Jun 17 '16 at 21:33
  • thanks for the update. I have a few issues with the snippet though: 1- on line 4, you probably mean `["b": b]` as the fallback value; 2- this code always performs 3 lookups of key `"a"` in `dict`. In theory, only one is required when `dict` already contains `"a"`, and two the first time `"a"` gets inserted. That's precisely the problem I'm trying to solve. – David Jun 20 '16 at 15:19
3

You could do something like this:

func with<T>(inout _ object: T, @noescape action: (inout T) -> ()) {
    action(&object)
}

That would let you write:

with(&dict["a"]) { subDict in
    subDict!["b"] = 4
    subDict!["c"] = 5
}
Hamish
  • 78,605
  • 19
  • 187
  • 280
zneak
  • 134,922
  • 42
  • 253
  • 328
  • `subDict` would be immutable within the closure, therefore this code doesn't currently compile, but it's totally possible to make the closure argument `inout` as you say (I can edit if you're okay with that). – Hamish Jun 16 '16 at 21:21
  • Interesting construct, made me aware of the `@noescape` attribute. However, one still needs to check for the existence of `dict["a"]` because the function crashes when the key isn't there, so additional lookups/copies are still required. – David Jun 17 '16 at 16:44
  • @David, yes, you would have to unwrap subDict. That said, as far as lookups go, this does at most two and perhaps just one. Optional unwrapping isn't a dictionary lookup and it is completely legal to assign a Dictionary to `subDict` even if it started out `nil`. – zneak Jun 17 '16 at 16:55
0

I ended up implementing a wrapper around a dictionary:

class HeapDict<K: Hashable, V> : SequenceType {
    var dict = [K : V]()
    subscript(index: K) -> V? {
        get {
            return dict[index]
        }
        set {
            dict[index] = newValue
        }
    }
    func generate() -> DictionaryGenerator<K, V> {
        return dict.generate()
    }
}

This allows for very simple modification of the nested dictionary:

var dict2 = [String : HeapDict<String, Int>]()
var dd = dict2["a"]
if dd == nil {
    dd = HeapDict()
    dict2["a"] = dd
}
dd!["b"] = 1

From my point of view this addresses my original issue with, I believe, very little overhead. Whether it's a good thing to manipulate collections this way might be another discussion though, and my reason for doing it might simply be my background with other programming languages. Thanks for the answers! :-)

David
  • 437
  • 1
  • 4
  • 15