Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modifying an array of dictionaries in Swift

I’m new to Swift and have been having some troubles figuring out some aspects of Arrays and Dictionaries.

I have an array of dictionaries, for which I have used Type Aliases - e.g.

typealias myDicts = Dictionary<String, Double>

var myArray : [myDicts] = [
["id":0,
    "lat”:55.555555,
    "lng”:-55.555555,
    "distance":0],
["id":1,
    "lat": 44.444444,
    "lng”:-44.444444,
    "distance":0]
]

I then want to iterate through the dictionaries in the array and change the “distance” key value. I did it like this:

for dict:myDicts in myArray {

dict["distance"] = 5

}

Or even specifically making sure 5 is a double with many different approaches including e.g.

for dict:myDicts in myArray {

let numberFive : Double = 5

dict["distance"] = numberFive

}

All my attempts cause an error:

 @lvalue $T5' is not identical to '(String, Double)

It seems to be acting as if the Dictionaries inside were immutable “let” rather than “var”. So I randomly tried this:

for (var dict:myDicts) in myArray {

dict["distance"] = 5

}

This removes the error and the key is indeed assigned 5 within the for loop, but this doesn't seem to actually modify the array itself in the long run. What am I doing wrong?

like image 718
Dana Avatar asked Jan 10 '23 18:01

Dana


2 Answers

The implicitly declared variable in a for-in loop in Swift is constant by default (let), that's why you can't modify it directly in the loop.

The for-in documentation has this:

for index in 1...5 {
    println("\(index) times 5 is \(index * 5)")
}

In the example above, index is a constant whose value is automatically set at the start of each iteration of the loop. As such, it does not have to be declared before it is used. It is implicitly declared simply by its inclusion in the loop declaration, without the need for a let declaration keyword.

As you've discovered, you can make it a variable by explicitly declaring it with var. However, in this case, you're trying to modify a dictionary which is a struct and, therefore, a value type and it is copied on assignment. When you do dict["distance"] = 5 you're actually modifying a copy of the dictionary and not the original stored in the array.

You can still modify the dictionary in the array, you just have to do it directly by looping over the array by index:

for index in 0..<myArray.count {
    myArray[index]["distance"] = 5
}

This way, you're sure to by modifying the original dictionary instead of a copy of it.

That being said, @matt's suggestion to use a custom class is usually the best route to take.

like image 58
Mike S Avatar answered Jan 16 '23 20:01

Mike S


You're not doing anything wrong. That's how Swift works. You have two options:

  • Use NSMutableDictionary rather than a Swift dictionary.

  • Use a custom class instead of a dictionary. In a way this is a better solution anyway because it's what you should have been doing all along in a situation where all the dictionaries have the same structure.

The "custom class" I'm talking about would be a mere "value class", a bundle of properties. This was kind of a pain to make in Objective-C, but in Swift it's trivial, so I now do this a lot. The thing is that you can stick the class definition for your custom class anywhere; it doesn't need a file of its own, and of course in Swift you don't have the interface/implementation foo to grapple with, let alone memory management and other stuff. So this is just a few lines of code that you can stick right in with the code you've already got.

Here's an example from my own code:

class Model {
    var task : NSURLSessionTask!
    var im : UIImage!
    var text : String!
    var picurl : String!
}

We then have an array of Model and away we go.

So, in your example:

class MyDict : NSObject {
    var id = 0.0
    var lat = 0.0
    var lng = 0.0
    var distance = 0.0
}

var myArray = [MyDict]()

let d1 = MyDict()
d1.id = 0
d1.lat = 55.55
d1.lng = -55.55
d1.distance = 0
let d2 = MyDict()
d2.id = 0
d2.lat = 44.44
d2.lng = -44.44
d2.distance = 0
myArray = [d1,d2]

// now we come to the actual heart of the matter

for d in myArray {
    d.distance = 5
}
println(myArray[0].distance) // it worked
println(myArray[1].distance) // it worked
like image 27
matt Avatar answered Jan 16 '23 21:01

matt