For Loop for Dictionary Don't Follow It's Order in Swift

Preserve order of dictionary items as declared in Swift?

In your case an array of custom objects might be more appropriate.
Here is a simple example that should help to get you started:

struct Unit : Printable {
let name: String
let factor: Double

// println() should print just the unit name:
var description: String { return name }
}


let units = [
Unit(name: "kg", factor: 1000.0),
Unit(name: "g", factor: 1.0),
Unit(name: "mg", factor: 0.001),
Unit(name: "lb", factor: 453.592292),
Unit(name: "oz", factor: 28.349523)
]

println(units) // [kg, g, mg, lb, oz]

(I am not sure if the non-metric unit factors are correct :)

Iterating Through a Dictionary in Swift

Dictionaries in Swift (and other languages) are not ordered. When you iterate through the dictionary, there's no guarantee that the order will match the initialization order. In this example, Swift processes the "Square" key before the others. You can see this by adding a print statement to the loop. 25 is the 5th element of Square so largest would be set 5 times for the 5 elements in Square and then would stay at 25.

let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25]
]
var largest = 0
for (kind, numbers) in interestingNumbers {
println("kind: \(kind)")
for number in numbers {
if number > largest {
largest = number
}
}
}
largest

This prints:

kind: Square
kind: Prime
kind: Fibonacci

iterating over a dictionary in Swift

If you add a print of names and numbers directly in the outer loop you will realise there is no need to loop over names as you do and neither is it necessary to try to use the dictionary again inside the loop.

So we could skip one loop ands directly use the names variable to remember the name of the largest value array. And furthermore there is no reason to loop over the numbers array either since Array has a max() function we can use.

So the code becomes

for (names, numbers) in interestingNumbers {
if let max = numbers.max(), max > largest {
largest = max
largestName = names
}
}

An even short way to do it is to use some high-order functions to get the max value

interestingNumbers.mapValues({ $0.max() ?? 0 }).max(by: { $0.value < $1.value}) 

This will return a tuple and can be used like this

if let tuple = interestingNumbers.mapValues({ $0.max() ?? 0 }).max(by: { $0.value < $1.value})  {
largest = tuple.1
largestName = tuple.0
}

Swift - Stored values order is completely changed in Dictionary

This is because of the definition of Dictionaries:

Dictionary

A dictionary stores associations between keys of the same type and values of the same type in an collection with no defined ordering.

There is no order, they might come out differently than they were put in.
This is comparable to NSSet.


Edit:

NSDictionary

Dictionaries Collect Key-Value Pairs. Rather than simply maintaining an ordered or unordered collection of objects, an NSDictionary stores objects against given keys, which can then be used for retrieval.

There is also no order, however there is sorting on print for debugging purposes.

Loop through a dictionary in swift

This isn't a loop through a dictionary. It's looping though an array stored in one of the dictionaries keys. This is what would want to do if for example you had an array of strings as one of your dictionary's keys.

if let arr = dict["Files"] as? [String] {
for file in arr {

}
}

If you do want to just loop through the dictionary though, this is possible in Swift, and can be done like this:

for (key, value) in dict {
println("key is - \(key) and value is - \(value)")
}

Iterate through Dictionary over all levels

Assuming you have Dictionary with type [String:Any], function to flatten it will be something like:

    func flatten(_ obj:[String:Any]) -> [String:Any] {
var result = [String:Any]()
for (key, val) in obj {
if let v = val as? [String:Any] {
result = result.merging(flatten(v), uniquingKeysWith: {
$1
})
}
//I also included initial value of internal dictionary
/if you don't need initial value put next line in 'else'
result[key] = val
}
return result
}

To use that:

    let d:[String:Any] = [
"test1":1,
"test2":2,
"test3":[
"test3.1":31,
"test3.2":32
]
]
let res = flatten(d)
print(res)

["test2": 2, "test3.2": 32, "test3.1": 31, "test1": 1, "test3":
["test3.2": 32, "test3.1": 31]]

note: dictionaries are not sorted structures

SwiftUI iterating through dictionary with ForEach

Simple answer: no.

As you correctly pointed out, a dictionary is unordered. The ForEach watches its collection for changes. These changes includes inserts, deletions, moves and update. If any of those changes occurs, an update will be triggered. Reference: https://developer.apple.com/videos/play/wwdc2019/204/ at 46:10:

A ForEach automatically watches for changes in his collection

I recommend you watch the talk :)

You can not use a ForEach because:

  1. It watches a collection and monitors movements. Impossible with an unorered dictionary.
  2. When reusing views (like a UITableView reuses cells when cells can be recycled, a List is backed by UITableViewCells, and I think a ForEach is doing the same thing), it needs to compute what cell to show. It does that by querying an index path from the data source. Logically speaking, an index path is useless if the data source is unordered.

Sort Dictionary by keys


let dictionary = [
"A" : [1, 2],
"Z" : [3, 4],
"D" : [5, 6]
]

let sortedKeys = Array(dictionary.keys).sorted(<) // ["A", "D", "Z"]

EDIT:

The sorted array from the above code contains keys only, while values have to be retrieved from the original dictionary. However, 'Dictionary' is also a 'CollectionType' of (key, value) pairs and we can use the global 'sorted' function to get a sorted array containg both keys and values, like this:

let sortedKeysAndValues = sorted(dictionary) { $0.0 < $1.0 }
println(sortedKeysAndValues) // [(A, [1, 2]), (D, [5, 6]), (Z, [3, 4])]

EDIT2: The monthly changing Swift syntax currently prefers

let sortedKeys = Array(dictionary.keys).sort(<) // ["A", "D", "Z"]

The global sorted is deprecated.



Related Topics



Leave a reply



Submit