Reputation: 36158
When creating a key/value dictionary, it is returned as randomly sorted. I was hoping that they would be in the same order as it was created.
For example, see this code:
var dict = [
"kg": 1,
"g": 2,
"mg": 3,
"lb": 4,
"oz": 5,
"t": 6
]
println(dict)
This returns the following:
[kg: 1, oz: 5, g: 2, mg: 3, lb: 4, t: 6]
How do I preserve the order in which the dictionary was declared?
Upvotes: 28
Views: 20464
Reputation: 54641
At WWDC21 Apple announced the Collections
package that includes OrderedDictionary
(among others).
Here's an example (you only need to specify the type explicitly):
var dict: OrderedDictionary = [
"kg": 1,
"g": 2,
"mg": 3,
"lb": 4,
"oz": 5,
"t": 6
]
Note: Collections
is available as a separate package, so you need to import it to your project.
You can find more information here:
Upvotes: 5
Reputation: 336
I have faced the same issue and solved it by this class https://github.com/noman200898/OrderedDictionary/blob/master/Sources/OrderedDictionary.swift Simple add this class to your project and use it like below:
let testDic: OrderedDictionary = ["kg": 1, "g": 2, "mg": 3, "lb": 4, "oz": 5, "t": 6]
print(testDic)
print(testDic.unorderedDictionary)
print(testDic.orderedKeys)
print(testDic.orderedValues)
Hope it will help.
Upvotes: 1
Reputation: 3085
Swift 5.1:
Use a KeyValuePairs instance when you need an ordered collection of key-value pairs and don’t require the fast key lookup that the Dictionary type provides.
You initialize a KeyValuePairs instance using a Swift dictionary literal. Besides maintaining the order of the original dictionary literal, KeyValuePairs also allows duplicates keys. For example:
let recordTimes: KeyValuePairs = ["Florence Griffith-Joyner": 10.49,
"Evelyn Ashford": 10.76,
"Evelyn Ashford": 10.79,
"Marlies Gohr": 10.81]
print(recordTimes.first!)
// Prints "("Florence Griffith-Joyner", 10.49)"
Upvotes: 44
Reputation: 83
One thing you can do is use a enum for the key values instead of string literals. If the enum has Int rawValue you can write a function that returns a key based on a raw value. You can then use a for-loop to get the declared order. For example
enum Months: Int{ case jan , feb, mar, apr }
let dict: Dictionary<Months, String> = [
.jan: "One",
.feb: "Two",
.mar: "Three",
.apr : "Four"
]
// This function returns key from enum rawValue. -- requires enum as key type with Int rawValue, but works for any value type.
func keyWithRawValue(_ rawValue: Int) -> Any? {
var key: Any?
for element in dict {
let rawAtElement = element.key.rawValue
if rawValue == rawAtElement {
key = element.key
}
}
return key
}
// use this for iterating through in declared order.
for index in 0...dict.count - 1 {
let key = keyWithRawValue(index) as! Months
print(dict[key]! )
}
outputs:
One
Two
Three
Four
Upvotes: 0
Reputation: 290
I decided to solve this problem this way:
I added to my Object that own Dictionary Array with String values
var sections: Dictionary> = Dictionary()
var orderedKeys : Array = Array()
When adding item to dictionary, but 'Key' is not created yet (1st item with such key) I add key into orderedKeys
if sections[sectionName] != nil {
sections[sectionName]?.append(item)
} else { // 1st item with such key
sections[sectionName] = [item]
orderedKeys.append(sectionName)
}
Then when I need to get something from that Dictionary I use my orderedKeys instead of default allKeys
func getSectionById(sectionId : Int) {
let sectionName = orderedKeys[sectionId]
return (selectedPlaylist?.sections[sectionName])!
}
But probably, if applicable for your case, better solution is to create Array with Structs:
var mySections : Array<Section> = Array()
Struct Section {
let title: String
let objects: Array
}
Upvotes: 1
Reputation: 1377
Sadly, Apple has only built in three collection datastructures in to Swift out of the box. These is the Array, Dictionary and Set. Luckily, Apple has introduced a very extensive and powerful hierarchy of protocols that support defining your own collection classes easily and elegantly.
So, if you do not mind the additional space- (and perhaps time-) complexity of your solution, you might want to opt for building your own Swift collection class/struct that resembles a Dictionary that preserves the order in which elements were added to it, by relating indexes to keys and vice versa. See the documentation for more information on creating your own collection datastructures: https://developer.apple.com/documentation/swift/collection.
I'll give you a little something to get you going:
Disclaimer: This code is not tested, and I have not spent a great deal of thought on matters of algorithmic complexity. Please determine the requirements of your solution and check for yourself whether the following code complies.
public struct KeepOrderDictionary<Key, Value> where Key : Hashable
{
public private(set) var values: [Value]
fileprivate var keyToIndexMap: [Key:Int]
fileprivate var indexToKeyMap: [Int:Key]
public init()
{
self.values = [Value]()
self.keyToIndexMap = [Key:Int]()
self.indexToKeyMap = [Int:Key]()
}
public var count: Int
{ return values.count}
public mutating func add(key: Key, _ value: Value)
{
if let index = keyToIndexMap[key]
{ values[index] = value}
else
{
values.append(value)
keyToIndexMap[key] = values.count - 1
indexToKeyMap[values.count - 1] = key
}
}
public mutating func add(index: Int, _ value: Value) -> Bool
{
if let key = indexToKeyMap[index]
{
add(key: key, value)
return true
}
return false
}
public func get(key: Key) -> (Key, Value)?
{
if let index = keyToIndexMap[key]
{ return (key, values[index])}
return nil
}
public func get(index: Int) -> (Key, Value)?
{
if let key = indexToKeyMap[index]
{ return (key, values[index])}
return nil
}
public mutating func removeValue(forKey key: Key) -> Bool
{
guard let index = keyToIndexMap[key] else
{ return false}
values.remove(at: index)
keyToIndexMap.removeValue(forKey: key)
indexToKeyMap.removeValue(forKey: index)
return true
}
public mutating func removeValue(at index: Int) -> Bool
{
guard let key = indexToKeyMap[index] else
{ return false}
values.remove(at: index)
keyToIndexMap.removeValue(forKey: key)
indexToKeyMap.removeValue(forKey: index)
return true
}
}
extension KeepOrderDictionary
{
public subscript(key: Key) -> Value?
{
get
{ return get(key: key)?.1}
set
{
if let newValue = newValue
{ add(key: key, newValue)}
else
{ let _ = removeValue(forKey: key)}
}
}
public subscript(index: Int) -> Value?
{
get
{ return get(index: index)?.1}
set
{
if let newValue = newValue
{ let _ = add(index: index, newValue)}
}
}
}
extension KeepOrderDictionary : ExpressibleByDictionaryLiteral
{
public init(dictionaryLiteral elements: (Key, Value)...)
{
self.init()
for entry in elements
{ add(key: entry.0, entry.1)}
}
}
extension KeepOrderDictionary : Sequence
{
public typealias Iterator = IndexingIterator<[(key: Key, value: Value)]>
public func makeIterator() -> KeepOrderDictionary.Iterator
{
var content = [(key: Key, value: Value)]()
for i in 0 ..< count
{
if let value: Value = self[i], let key: Key = indexToKeyMap[i]
{ content.append((key: key, value: value))}
}
return content.makeIterator()
}
}
Upvotes: 4
Reputation: 2902
struct MenueItem {
let title : String
let image : String}
let items = [MenueItem(title: "Readers", image: "multimedia"),
MenueItem(title: "Live Broadcast", image: "multimedia"),
MenueItem(title: "Quran Browsing", image: "multimedia"),
MenueItem(title: "Personal Quran", image: "multimedia"),
MenueItem(title: "Videos", image: "multimedia"),
MenueItem(title: "Favorites", image: "favorites")]
....
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
....
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MenuTableViewCell", forIndexPath: indexPath) as! MenuTableViewCell
cell.cellTitle.text = items[indexPath.row].title
cell.cellImage.image = UIImage(named: items[indexPath.row].image)
return cell
}
}
Upvotes: -2
Reputation: 540065
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 :)
Upvotes: 28
Reputation: 2093
As Apple says:
Dictionaries are unordered collections of key-value associations.
Don't know if that will help you but in this link there is an implementation of an ordereddictionary: http://www.raywenderlich.com/82572/swift-generics-tutorial
Upvotes: 5