Ordered map in Swift - ios

Is there any built-in way to create an ordered map in Swift 2? Arrays [T] are sorted by the order that objects are appended to it, but dictionaries [K : V] aren't ordered.
For example
var myArray: [String] = []
myArray.append("val1")
myArray.append("val2")
myArray.append("val3")
//will always print "val1, val2, val3"
print(myArray)
var myDictionary: [String : String] = [:]
myDictionary["key1"] = "val1"
myDictionary["key2"] = "val2"
myDictionary["key3"] = "val3"
//Will print "[key1: val1, key3: val3, key2: val2]"
//instead of "[key1: val1, key2: val2, key3: val3]"
print(myDictionary)
Are there any built-in ways to create an ordered key : value map that is ordered in the same way that an array is, or will I have to create my own class?
I would like to avoid creating my own class if at all possible, because whatever is included by Swift would most likely be more efficient.

You can order them by having keys with type Int.
var myDictionary: [Int: [String: String]]?
or
var myDictionary: [Int: (String, String)]?
I recommend the first one since it is a more common format (JSON for example).

Just use an array of tuples instead. Sort by whatever you like. All "built-in".
var array = [(name: String, value: String)]()
// add elements
array.sort() { $0.name < $1.name }
// or
array.sort() { $0.0 < $1.0 }

"If you need an ordered collection of key-value pairs and don’t need the fast key lookup that Dictionary provides, see the DictionaryLiteral type for an alternative." - https://developer.apple.com/reference/swift/dictionary

You can use KeyValuePairs,
from documentation:
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.
let pairs: KeyValuePairs = ["john": 1,"ben": 2,"bob": 3,"hans": 4]
print(pairs.first!)
//prints (key: "john", value: 1)

if your keys confirm to Comparable, you can create a sorted dictionary from your unsorted dictionary as follows
let sortedDictionary = unsortedDictionary.sorted() { $0.key > $1.key }

As Matt says, dictionaries (and sets) are unordered collections in Swift (and in Objective-C). This is by design.
If you want you can create an array of your dictionary's keys and sort that into any order you want, and then use it to fetch items from your dictionary.
NSDictionary has a method allKeys that gives you all the keys of your dictionary in an array. I seem to remember something similar for Swift Dictionary objects, but I'm not sure. I'm still learning the nuances of Swift.
EDIT:
For Swift Dictionaries it's someDictionary.keys

You can use the official OrderedDictionary from the original Swift Repo
The ordered collections currently contain:
Ordered Dictionary (That you are looking for)
Ordered Set
They said it is going to be merged in the Swift itself soon (in WWDC21)

Swift does not include any built-in ordered dictionary capability, and as far as I know, Swift 2 doesn't either
Then you shall create your own. You can check out these tutorials for help:
http://timekl.com/blog/2014/06/02/learning-swift-ordered-dictionaries/
http://www.raywenderlich.com/82572/swift-generics-tutorial

I know i am l8 to the party but did you look into NSMutableOrderedSet ?
https://developer.apple.com/reference/foundation/nsorderedset
You can use ordered sets as an alternative to arrays when the order of
elements is important and performance in testing whether an object is
contained in the set is a consideration—testing for membership of an
array is slower than testing for membership of a set.

var orderedDictionary = [(key:String, value:String)]()

As others have said, there's no built in support for this type of structure. It's possible they will add an implementation to the standard library at some point, but given it's relatively rare for it to be the best solution in most applications, so I wouldn't hold your breath.
One alternative is the OrderedDictionary project. Since it adheres to BidirectionalCollection you get most of the same APIs you're probably used to using with other Collection Types, and it appears to be (currently) reasonably well maintained.

Here's what I did, pretty straightforward:
let array = [
["foo": "bar"],
["foo": "bar"],
["foo": "bar"],
["foo": "bar"],
["foo": "bar"],
["foo": "bar"]
]
// usage
for item in array {
let key = item.keys.first!
let value = item.values.first!
print(key, value)
}
Keys aren't unique as this isn't a Dictionary but an Array but you can use the array keys.

use Dictionary.enumerated()
example:
let dict = [
"foo": 1,
"bar": 2,
"baz": 3,
"hoge": 4,
"qux": 5
]
for (offset: offset, element: (key: key, value: value)) in dict.enumerated() {
print("\(offset): '\(key)':\(value)")
}
// Prints "0: 'bar':2"
// Prints "1: 'hoge':4"
// Prints "2: 'qux':5"
// Prints "3: 'baz':3"
// Prints "4: 'foo':1"

Related

Swift 3: how to sort Dictionary's key and value of struct

This is my Struct,by Swift 3. I know the Dictionary is not stored sequence like an Array and that is my problem. I want to get my Dictionary sequence as I set in ViewArray. I can get the ctC Dictionary, but how can i sort the keys or values as i set in ViewArrayplease and appreciate the help.
struct CTArray {
var ctname: String
var ctkey: String
var ctC: [String:String]
}
var ViewArray:[CTArray] = []
ViewArray += [CTArray(ctname: "kerish", ctkey: "KH", ctC: ["mon":"Apple", "kis":"aone", "Bat":"Best", "orlno":"bOne"])]
ViewArray += [CTArray(ctname: "tainers", ctkey: "TNN", ctC: ["letGor":"one", "washi":"testing", "monk":"lasth"])]
ViewArray += [CTArray(ctname: "techiu", ctkey: "TCU", ctC: ["22":"tt", "wke":"303", "lenth":"highest"])]
i want to show them in my TableView Cell sorted like these:
the ViewArray[0].ctC.key sorted like [mon, kis, Bat, orlno]
the ViewArray[1].ctC.key sorted like [letGor, washi, monk]
the ViewArray[2].ctC.value sorted like [tt, 303, highest]
It is not clear to me what you are asking, but I'll offer this in case it helps.
I know the Dictionary is not stored sequence like an Array and that is my problem.
If you want to access a dictionary by a certain order of its keys then you can create an array of just the keys in the order you required and use that to access the dictionary. An example is probably easier to follow:
Starting with one of your dictionaries:
let dict = ["mon":"Apple", "kis":"aone", "Bat":"Best", "orlno":"bOne"]
print(dict)
this output:
["Bat": "Best", "kis": "aone", "orlno": "bOne", "mon": "Apple"]
which is not what you want. Now introduce a key array and use that to access the dictionary:
let keyOrder = ["mon", "kis", "Bat", "orlno"]
for key in keyOrder
{
print("\(key): \(dict[key]!)")
}
this outputs:
mon: Apple
kis: aone
Bat: Best
orlno: bOne
which is the order you wish.
The same idea can be used anywhere you want to use/show/etc. the keys in a particular order, by using the keyOrder array as part of dictionary access you are making it appear as though the dictionary entries are "stored in sequence" as you put it.
HTH
var object1 = viewArray[0].ctC.flatMap({$0.key})
var object2 = viewArray[1].ctC.flatMap({$0.key})
var object3 = viewArray[2].ctC.flatMap({$0.value})
print(object1)
print(object2)
print(object3)
Outputs:
["Bat", "kis", "orlno", "mon"]
["letGor", "monk", "washi"]
["highest", "tt", "303"]

Iterating through an array of dictionaries to get all the keys

I have an array of dictionaries [[String : AnyObject]] called rooms in a User object - each dictionary holds a name as the String and an id as the AnyObject.
I want to populate a table view with the names, so I'm trying to loop through the array and grab the String values from the dicts.
if let roomDict = myUser.rooms as? [[String : AnyObject]] {
for (roomNames, _) in roomDict {
cell.textLabel?.text = roomNames
}
}
I'm relatively new and from what I've seen in tutorials and such when looping through dictionaries, you use the underscore to specify that you don't want the second value. So just grab all the first values (in this case, the names), and set them to the roomNames variable.
However I'm confused here because I'm not looping through a dictionary, I'm looping through an array of dictionaries. So I'm not sure how to do that. I did a search and the results I saw generally were asking about JSON, which isn't the case here. How can I do this?
Thanks for any help!
Firstly, your dictionary structure isn't ideal. Rather than having the key as the room name and the value as the identifier, your dictionary should have known key names, with variable values. Keys should not be "data" in a dictionary.
So, rather than
["room1":1]
it would be better if it were
["roomName":"room1", "roomID":1]
with your current structure, however, assuming that there is only one key per dictionary and that is the room name, you can get the names with:
if let rooms = myUser.rooms as? [[String : AnyObject]] {
roomNames = rooms.map({ $0.keys.first!})
}
If you use the better structure I suggested then it would be
if let rooms = myUser.rooms as? [[String : AnyObject]] {
roomNames = rooms.map({ $0["roomName"] as? String ?? ""})
}
I'm not clear about your data structure. If you have an array of dictionaries, and you want all the keys, you could use code like this:
let array = [
["key1": "value1",
"key2": "value2",
"key3": "value3",
"key4": "value4"],
["key5": "value5",
"key6": "value6",
"key7": "value7",
"key8": "value8"],
["key9": "value9",
"key10": "value10",
"key11": "value11",
"key12": "value12"]
]
let keys = array
.map{$0.keys}
.reduce([], +)
print(keys)
That will give you an array of all the keys from all the dictionaries. The keys from each dictionary will be in a jumbled order however, since dictionaries are unordered. You'll get the keys from each inner dictionary in a jumbled order, followed by the keys from the next dictionary in the array, etc.
Sample output:
["key2", "key3", "key4", "key1", "key7", "key8", "key5", "key6", "key9", "key10", "key12", "key11"]
If you want to sort the keys, you can do that:
let keys = array
.map{$0.keys}
.reduce([], +)
.sorted{$0.compare($1, options: .numeric) == .orderedAscending}
(In the above code I'm using the String class's compare(_:options:) function with an options value of .numeric so that "key11" sorts after "key10" instead of ["key1", "key11", "key12", "key2"], which you get from standard string ordering.)
The output of the sorted version (with the .numeric option) is:
["key1", "key2", "key3", "key4", "key5", "key6", "key7", "key8", "key9", "key10", "key11", "key12"]
Without the .numeric option, the output would be:
["key1", "key10", "key11", "key12", "key2", "key3", "key4", "key5", "key6", "key7", "key8", "key9"]
If your keys contain mixed upper/lower case and you want to ignore it, you'd use options of [.numeric, .caseInsensitive] (case insensitive sorting where numbers within strings are compared using numeric value.)

How to sort array based on another arrays position?

I have a tableView with its style being Right Detail. I therefore have 2 arrays, one is for the textLabels data, and the other is for detailTextLabel.
There will be 2 "sort by" options. One will be sort by the textLabels data, and the second will sort by the detailTextlabels data. So when I sort the first array (textLabels array), the second array (detailTextLables array) will also have to get sorted based on the firstarray`.
I know how to sort arrays, but how can I sort one array based on another?
Here's how I sorted the array: (it's an array of Dates.
firstArray.sort({ (a, b) -> Bool in
a.earlierDate(b) == a
})
First, why not have two arrays? Because you only have one array of UITableViewCells and you want to keep all the data associated with a particular table view cell together. And it makes the need to try to coordinate the sorting of multiple arrays (what you are asking to do) unnecessary.
But if you really want to do that:
var array1 = ["1", "3", "2"]
var array2 = ["One", "Three", "Two"]
let sortedFoo = zip(array1, array2).sort { $0.0 < $1.0 }
array1 = sortedFoo.map { $0.0 }
array2 = sortedFoo.map { $0.1 }
The idea with the above code is that it combines the two arrays into one array of tuples, and then sorts them based on the elements in the first array, then breaks that single array back out into two separate arrays.
In other words, since you have to combine the two arrays into one to do the sort anyway, you might as well make them in a single array in the first place. :-)
It's a bit messy, but you can use enumerate to work with indices and elements at the same time:
array1.enumerate().sort {
return $0.element < $1.element
}.map {$0.element}
array1.enumerate().sort {
return array2[$0.index] < array2[$1.index]
}.map {$0.element}
But it's really much simpler/easier with one array.
struct Item {
let prop1: Int
let prop2: String
}
var array = [
Item(prop1: 1, prop2: "c"),
Item(prop1: 2, prop2: "b"),
Item(prop1: 3, prop2: "a")
]
array.sort { $0.prop1 < $1.prop1 }
array.sort { $0.prop2 < $1.prop2 }
How about using using positionOf on the sorted array to find the corresponding index in the I sorted array?

How to push data with multiple types in Firebase?

I want to push an array that has strings, numbers and date. Do I have to update individually or is there another way I can accomplish this?
Example:
var categories : [String] = self.parseCategories()
var standbyDataStrings = [
"firstName":firstName,
"lastName":lastName,
"categories": categories,
"time_stamp":self.date.timeIntervalSince1970
]
var standbyDataNums = [
"radius":nf.numberFromString(self.helpRadiusLabel.text!),
"duration":nf.numberFromString(self.helpDurationLabel.text!)
]
standbyUserRef.updateChildValues(standbyDataStrings)
standbyUserRef.updateChildValues(standbyDataNums) // this gives me a error "string is not identical to NSObject"
Combining standByDataStrings and standbyDataNums gives me an error.
Or is there a way to retrieve a string from Firebase and using it as an int. It gets stored as a String with the quotations.
The Firebase API expects an NSDictionary for updateChildValues. A NSDictionary can't contain nil values. A normal Swift dictionary on the other hand can contain nil values.
The return type of numberFromString is NSNumber?, so Swift infers that the dictionary might contain nil and so it can't be passed to updateChildValues. By explicitly forcing non-nil values with ! you can make this code compile:
var standbyDataNums = [
"radius":nf.numberFromString(self.helpRadiusLabel.text!)!,
"duration":nf.numberFromString(self.helpDurationLabel.text!)!
]
standbyUserRef.updateChildValues(standbyDataNums) // now type checks

Swift dictionary with array as value

How do you declare a dictionary that has an array as the value? Is this even possible?
Yes
let myDictionary: [String: [Int]] = ["Hello": [1, 2, 3], "World": [4, 5, 6]]
In fact, you don't even need the explicit type declaration if you assign an initial value in place. It can go as simple as:
let myDictionary = ["Hello": [1, 2, 3], "World": [4, 5, 6]]
To use the value:
println(myDictionary["Hello"][0]) // Print 1
println(myDictionary["World"][0]) // Print 4
var dictionary : [String:[AnyObject]]
var dictionary2 = [String:[AnyObject]]()
You can change AnyObject for any class or use it like AnyObject itself if you don't know the class that will be in the array.
If you want to store for example an array of strings:
var dict: [String: [String]]
or without syntactic sugar:
var dict: Dictionary<String, Array<String>>
Dictionaries, like arrays and more generally whatever uses generics, can handle anything that is a swift type, including tuples, closures, dictionaries, dictionaries of dictionaries, arrays of dictionaries, etc. - unless conditions are specified for the generic type (for instance, a dictionary key can be any type that implements the Hashable protocol), and in that case types must conform to the constraints.

Resources