Iterating through an array of dictionaries to get all the keys - ios

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.)

Related

Sort dictionary of arrays by both key and values in Swift

I have a dictionary like this:
dict = {
"a": ["apple", "aeroplane", "ash"],
"c": ["cat", "car"],
"b": ["boy", "bit", "bee"]
}
I would like to sort it using swift and the result should be like this:
dict = {
"a": ["aeroplane", "apple", "ash"],
"b": ["bee", "bit", "boy"],
"c": ["car", "cat"]
}
Both the keys and the values in the array should be in alphabetical order. However, I can only successfully sort the keys using .keys.sorted() and I failed to sort the array in each of the dictionary values in alphabetical order.
First of all I just want to remark that it's a bit redundant to sort the keys of a dictionary because you check values associated with a given key, not where the value is, it makes no sense to order them.
Second about sorting the arrays inside the dictionary it's pretty easy, first you need to iterate over the dictionary using for each, then since the arrays are immutable after you call .sorted() you assign the result to the key associated with the value you just received.
var dict : [String:[String]] = [
"a" : ["apple", "aeroplane", "ash"],
"c" : ["cat", "car"],
"b" : ["boy", "bit", "bee"]
]
// sort arrays inside an item of the dictionary
for (key,value) in dict {
dict[key] = value.sorted()
}
print(dict)

check which dictionary keys corresponding arrays contain a certain string

I have a dictionary like this dict = [String:[String]]
I want to be able to check if a given string is contained in any of the arrays in the dictionary. If so I would like to then gather the keys for those arrays and create a new array of those values.
Here searching for the word "so":
let dict = ["hi":["so", "im"], "fi": ["to", "le"]]
let keys = Array(dict.filter{ $1.contains("so") }.keys)
print(keys)
For your second request where a match is enough:
let values = ["hi":["so", "im"], "fish": ["to", "ler"]]
let keys = Array(values.filter{ $1.contains{ string in string.contains("s") } }.keys)
print(keys)

Swift NSDictionary get 0th value without knowing key name

I have an NSDictionary that has a key like messageID5 and the value has three key/value pairs.
I know the NSDictionary only has 1 value in it because I limited my query to 1. But I don't know the name of the key. I just want the value, but I can't access it like an array [0]. You can access it just fine in PHP or Python. I've been trying a lot of different solutions for this basic problem, but a lot of them seem overly messy. anyValue[0] gives me a type error.
If you don't know your dictionary keys, you can get your NSDictionary allKeys.first property or allValues.first:
let dict = NSDictionary(dictionary: ["a":["b":1]])
let subDict = dict[dict.allKeys.first] as? [String:Any] ?? [:] // ["b": 1]
// or
let subDict = dict.allValues.first as? [String:Any] ?? [:] // ["b": 1]
The first thing to acknowledge is that key/value pairs in dictionaries does not maintain any specific order - this is required for an optimization in access to the contents of this structure.
As for your case if you're 100% sure you'll have only one value inside your dictionary you can use .allValues.first to retrieve the contained value. If your know that the type of your value is NSDictionary the whole code may look like this:
let childDictionary = rootDictionary.allValues.first as? NSDictionary
I suggest using (dictionary as Dictionary).values.first. That returns an optional, since it can fail if the dictionary is empty.
(Note that I edited this answer to cast the dictionary from an NSDictionary to a Dictionary so you an use the values property. NSDictionary doesn't have a values property, but Dictionary does.)

Swift: Sort Dictionary of Number Strings

I have a Dictionary of number String that looks like this:
let comph : [String : [String : String]] = [
"1":["title" : "first title"],
"2":["title" : "second title"],
"10": ["title" : "last title"]
]
Then when I sort my Dictionary using comph.sort { $0.0 < $1.0 } I get this:
let sortedComph = comph.sort { $0.0 < $1.0 }
print(sortedComph) //["1":["title" : "first title"],"10": ["title" : "last title"],"2":["title" : "second title"]]
How can I sort the Dictionary so that it returns the keys in numerical order?
eg: ["1":["title" : "first title"],"2":["title" : "second title"],"10": ["title" : "last title"]]
Thanks'
Dictionaries in Swift, by definition, are unsorted collections. You'll need to use some other type if you want to maintain order.
But, it looks like this guy wrote an OrderedDictionary in Swift, if you're interested:
Learning Swift: Ordered Dictionaries
Or, a quick n' dirty solution would be to extract the keys to an array, sort those, then iterate over the array and do a dictionary lookup using the keys.
The sortedComph constant in your example is no longer a dictionary, but an array of (key, value) tuples. The problem you're running into is that when sorting strings, the comparison is done without treating the strings as numeric values. That's what you need to fix.
One way to handle this is by creating Int? instances from your strings in your comparison function:
let sortedComph = comph.sort { Int($0.0) < Int($1.0) }
// [("1", ["title": "first title"]), ("2", ["title": "second title"]), ("10", ["title": "last title"])]
That way the resulting array will be sorted by the integer value of what's in the string keys. Any non-Integer keys will be grouped at the beginning of the list, since that's how nil sorts.
The more robust way is probably to use the NSString.compare(:options:) method:
let sortedComph = comph.sort {
($0.0 as NSString).compare($1.0, options: .NumericSearch) == .OrderedAscending
}
// [("1", ["title": "first title"]), ("2", ["title": "second title"]), ("10", ["title": "last title"])]
if you want you could have an array of structure representing your data
struct Model
{
var key:String!
var title:String!
// etc...
}
Then declare the array of that structure:
var arrayOfStructure = [Model]()
Then you sorted it

Ordered map in Swift

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"

Resources