David
David

Reputation: 447

Swift: Reference ("pointer") to collection

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!

Upvotes: 4

Views: 1469

Answers (3)

Luca Angeletti
Luca Angeletti

Reputation: 59536

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.

Upvotes: 3

David
David

Reputation: 447

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! :-)

Upvotes: 0

zneak
zneak

Reputation: 138231

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
}

Upvotes: 3

Related Questions