Unexpected dictionary behavior

A "normal" for..in loop on an array handles the elements in the order you would expect. But not this one.


Go ahead and try this in a playground. Every time it runs, I get a different ordering of the keys and never get back 1,2,3,4,5 as entered. If I make another loop around the loop shown (as commented out), it'll give the same key order for each iteration. But a different key order appears when the whole thing is rerun.


Confused.


import UIKit

let dataTable: [String: [Double]] = [
"1Cat": [99.95, 0, 0.05, 0, 0, 0, 0.04],
"2Dog": [1, 1, 0, 98, 0, 0, 0.2],
"3Mouse": [49.0, 43.2, 3.9, 0, 0.2, 3.7, 0.05],
"4Gerbil": [40.3, 55.4, 3.0, 0, 0.4, 0.9, 0.05],
"5Fish": [7.7, 90.8, 1.2, 0, 0.1, 0.2, 0.05]]

for i in dataTable {
    print(i)
}

// for j in 1...4 {
// for i in dataTable {
//    print(i)
// }
//    print("\n")
// }

Accepted Reply

I don't understand your point


I ran this:

let dataTable: [String: [Double]] = [
"1Cat": [99.95, 0, 0.05, 0, 0, 0, 0.04],
"2Dog": [1, 1, 0, 98, 0, 0, 0.2],
"3Mouse": [49.0, 43.2, 3.9, 0, 0.2, 3.7, 0.05],
"4Gerbil": [40.3, 55.4, 3.0, 0, 0.4, 0.9, 0.05],
"5Fish": [7.7, 90.8, 1.2, 0, 0.1, 0.2, 0.05]]
 
for i in dataTable {
    print(i)
}
 
print("\n Second form\n")
for j in 1...4 {
    for i in dataTable {
        print(i)
    }
    print("\n")
}


And got same results for each:

(key: "4Gerbil", value: [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

(key: "1Cat", value: [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

(key: "3Mouse", value: [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

(key: "2Dog", value: [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

(key: "5Fish", value: [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])


Second form


(key: "4Gerbil", value: [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

(key: "1Cat", value: [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

(key: "3Mouse", value: [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

(key: "2Dog", value: [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

(key: "5Fish", value: [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])


SAME AFTER 3 times


I assume the same for you ?


Did a second run and got a different key ordezr:

(key: "2Dog", value: [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

(key: "3Mouse", value: [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

(key: "1Cat", value: [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

(key: "4Gerbil", value: [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

(key: "5Fish", value: [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])


Second form


(key: "2Dog", value: [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

(key: "3Mouse", value: [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

(key: "1Cat", value: [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

(key: "4Gerbil", value: [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

(key: "5Fish", value: [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])


SAME AFTER 3 times



This is absolutely normal. What did you expect ?


Your title says:

Unexpected for...in array behavior


But that's not in array, that's in dictionary.

So it is normal, order is not fixed in a dictionary and may change each time you invoke the dictionary.

Replies

I don't understand your point


I ran this:

let dataTable: [String: [Double]] = [
"1Cat": [99.95, 0, 0.05, 0, 0, 0, 0.04],
"2Dog": [1, 1, 0, 98, 0, 0, 0.2],
"3Mouse": [49.0, 43.2, 3.9, 0, 0.2, 3.7, 0.05],
"4Gerbil": [40.3, 55.4, 3.0, 0, 0.4, 0.9, 0.05],
"5Fish": [7.7, 90.8, 1.2, 0, 0.1, 0.2, 0.05]]
 
for i in dataTable {
    print(i)
}
 
print("\n Second form\n")
for j in 1...4 {
    for i in dataTable {
        print(i)
    }
    print("\n")
}


And got same results for each:

(key: "4Gerbil", value: [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

(key: "1Cat", value: [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

(key: "3Mouse", value: [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

(key: "2Dog", value: [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

(key: "5Fish", value: [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])


Second form


(key: "4Gerbil", value: [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

(key: "1Cat", value: [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

(key: "3Mouse", value: [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

(key: "2Dog", value: [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

(key: "5Fish", value: [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])


SAME AFTER 3 times


I assume the same for you ?


Did a second run and got a different key ordezr:

(key: "2Dog", value: [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

(key: "3Mouse", value: [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

(key: "1Cat", value: [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

(key: "4Gerbil", value: [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

(key: "5Fish", value: [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])


Second form


(key: "2Dog", value: [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

(key: "3Mouse", value: [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

(key: "1Cat", value: [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

(key: "4Gerbil", value: [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

(key: "5Fish", value: [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])


SAME AFTER 3 times



This is absolutely normal. What did you expect ?


Your title says:

Unexpected for...in array behavior


But that's not in array, that's in dictionary.

So it is normal, order is not fixed in a dictionary and may change each time you invoke the dictionary.

What Claude31 said plus…

If you want to iterate through the dictionary in the order defined by its keys, here’s a good way to do that:

for (k, v) in dataTable.sorted(by: { $0.0 < $1.0 }) {
    print(k, v)
}

This prints:

1Cat [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04]
2Dog [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2]
3Mouse [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05]
4Gerbil [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05]
5Fish [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05]

This approach avoids any redundant key lookups, force unwrapping optionals, and so on.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

"So it is normal, order is not fixed in a dictionary and may change each time you invoke the dictionary."


That's the part that was unexpected, to me at least. Now I know.

"If you want to iterate through the dictionary in the order defined by its keys, here’s a good way to do that:"


Thanks for that but unfortunately my "real" table isn't sorted in any systematic way. It may make perfect sense to the user but not to a sort algorithm. I need to come up with a clever workaround to preserve the order.

What orginal order do you want to "preserve" ?

Probably the best is to cenvert dict to array, then sort on the correct order for future us.


Could do this (I assume the original order is the alphabetic of the key):


let dataConverted = (dataTable.map() { ($0.key, $0.value)}).sorted(by: { $0.0 <= $1.0 } )
print("dataConverted\n", dataConverted)


and get


dataConverted

[("1Cat", [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04]), ("2Dog", [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2]), ("3Mouse", [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05]), ("4Gerbil", [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05]), ("5Fish", [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])]


And now, order is preserved


print("\n Third form\n")
for _ in 1...4 {
    for i in dataConverted {
        print(i)
    }
    print("\n")
}

gets:

Third form


("1Cat", [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

("2Dog", [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

("3Mouse", [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

("4Gerbil", [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

("5Fish", [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])



("1Cat", [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

("2Dog", [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

("3Mouse", [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

("4Gerbil", [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

("5Fish", [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])



("1Cat", [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

("2Dog", [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

("3Mouse", [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

("4Gerbil", [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

("5Fish", [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])



("1Cat", [99.95, 0.0, 0.05, 0.0, 0.0, 0.0, 0.04])

("2Dog", [1.0, 1.0, 0.0, 98.0, 0.0, 0.0, 0.2])

("3Mouse", [49.0, 43.2, 3.9, 0.0, 0.2, 3.7, 0.05])

("4Gerbil", [40.3, 55.4, 3.0, 0.0, 0.4, 0.9, 0.05])

("5Fish", [7.7, 90.8, 1.2, 0.0, 0.1, 0.2, 0.05])

"What orginal order do you want to "preserve" ?"


Imagine the list above without the leading integers which I used to emphasize the effect. I'd like the order of the animals to remain unchanged. They're in no sorted order except for the unknown user preference.


I think I'll just put the list of keys into a separate array at the same time the dictionary is initialized. The user never really sees the dictionary and I can use the list of keys to retain control over the order.


I've been reminded by all this that dictionaries are unordered. I remember reading that but now it has hit home. Is there a good reason for that? I mean, it seems like it would always be more useful to have an ordered list than not.

I advise not to build to separate arrays. It is always a pain to maintained synced.


What is the problem with the solution I propose (of course giving the right original order) ?


I mean, it seems like it would always be more useful to have an ordered list than not.

Arrays are done for this.

Dictionary uses hash of the key, and hash is computed each time.


Look here on how to use keyvaluepairs for your purpose also.

https://developer.apple.com/documentation/swift/dictionary

"What is the problem with the solution I propose (of course giving the right original order) ?"


Unless I'm mistaken that would sort the keys alphabetically and thus change the original, unsorted order. Remember to drop the leading integer.