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:
- It watches a collection and monitors movements. Impossible with an unorered dictionary.
- When reusing views (like a
UITableView
reuses cells when cells can be recycled, aList
is backed byUITableViewCells
, and I think aForEach
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
Naming Convention for Optional Binding
Cannot Add Alamofire to Swift Project
Swiftui Coordinator Not Updating the Containing View's Property
Launch Sudo Command from MACos App Swift
Converting Swift 2.3 to Swift 3.0 - Error, Cannot Invoke 'Datatask' with an Argument List of Type'
Swift: How to Get Form Values Using Eureka Form Builder
My Uiviewcontroller Is Not Filling the Entire Screen
How to Access Firebase Variable Outside Firebase Function
Fblpromises Framework Not Found
How to Set an Environment Object in Preview
Compare Three Values for Equality
Why Is Swift's Ternary Operator So Picky About Whitespace
Swift [1,2] Conforms to Anyobject But [Enum.A, Enum.B] Does Not
How to Animate Bordercolor Change in Swift
Nsurlerrordomain with Code=-1100
How to Make Uitextfield Behave Like a Uisearchbar in Swift
How to Convert Anyclass to a Specific Class and Init It Dynamically in Swift