TruMan1
TruMan1

Reputation: 36158

Preserve order of dictionary items as declared in Swift?

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

Answers (9)

pawello2222
pawello2222

Reputation: 54641

OrderedDictionary

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

Nomanur
Nomanur

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

Carlos Chaguendo
Carlos Chaguendo

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

siege097
siege097

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

RemembranceNN
RemembranceNN

Reputation: 290

I decided to solve this problem this way:

  1. I added to my Object that own Dictionary Array with String values

    var sections: Dictionary> = Dictionary()

    var orderedKeys : Array = Array()

  2. 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)
    }
    
  3. 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

Teun Kooijman
Teun Kooijman

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

Atef
Atef

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

Martin R
Martin R

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

Javier Flores Font
Javier Flores Font

Reputation: 2093

As Apple says:

Dictionaries are unordered collections of key-value associations.

Link: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html

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

Related Questions