How to Sort an array of dictionary arrays - arrays

I get categories from my database with this code:
db.collection(DatabaseRef.categories).document(DatabaseRef.categoriesDoc).getDocument { snap, error in
guard error == nil, let snap = snap else {
return
}
let data = snap.data()!
for (k,v) in data {
self.categories.append([Int(k) ?? 0 : v])
}
self.categoryCollectionView.reloadData()
}
Here is my categories variable:
var categories: [[Int: Any]] = []
// categories = [[1: "category1"], [3: "category3"], [2: "category2"]]
I would like to sort categories before I reload my collectionview. So the categories should look like the following instead:
// categories = [[1: "category1"], [2: "category2"], [3: "category3"]]
I tried the following:
let sortedCategories = categories.sorted{ $0.key > $1.key}
But I am getting this error: Unable to infer closure type in the current context

Since categories is a an array of dictionaries, $0 and $1 are dictionaries [Int: Any], not key value pairs (key: Int, value: Any), although they each contain exactly one key value pair.
Therefore, one way to access the one and only key value pair in each dictionary is .keys.first!.
categories.sorted(by: { $0.keys.first! < $1.keys.first! })
Frankly, I don't think [[Int: Any]] is a suitable data structure for categories. If there are no duplicate ks, you should just use a [Int: Any]. And you would insert the data this way:
for (k,v) in data {
self.categories[[Int(k) ?? 0] = v
}
You can use the original code you used for sorting:
categories.sorted{ $0.key > $1.key }
And after sorting, you will get a [(key: Int, value: Any)].

Related

Appending dictionary values to array in Swift

I have a dictionary of type
[String: Object], where MyObject is an array of AnotherObject. I need to have it sorted as I need to fill an UITableView with Keys.countnumber of sections and Object.count number of rows in each section. I put all the keys in an array, but when I try to append the values into another array I get Thread 1: EXC_BAD_ACCESS (code=1, address=0x8).
This is the code I'm using
var dictionary = [String: MyObject]()
var sortedKeys: [String]?
var sortedValues = [MyObject]()
func sortItems() {
self.sortedKeys = self.dictionary.keys.sorted(by: >)
let sortedDict = self.dictionary.sorted(by: {$0.key > $1.key})
for (_, value) in sortedDict {
print(value)
self.sortedValues.append(value)
}
}
In the for loop, when I don't try to append the values to the array, it prints all the sorted values, the problem comes when I want to have them in an Array.
Edit
The dictionary is like this:
struct Object: Decodable {
let elements: [AnotherObject]
}
struct AnotherObject: Decodable {
let time, Id, Status: Int?
let date, startTime, endTime: String?
}
dictionary: [String: Object]
So the keys are numbers (representing days) and every day has an Object with (at least) one anotherObject.
I get the JSON from the API.
Thanks in advance
You should only sort the keys and then use that array to select from the dictionary and append your sortedValues array. I made the sortedKeys into a local variable
func sortItems() {
let sortedKeys = self.dictionary.keys.sorted(by: >)
for key in sortedKeys {
if let obj = dictionary[key] {
self.sortedValues.append(obj)
}
}
}
I don't know if this will make a difference in regard to the crash but another way is to let the function return an array
func sortItems() -> [Object] {
let sortedKeys = self.dictionary.keys.sorted(by: >)
var result: [Object]()
for key in sortedKeys {
if let obj = dictionary[key] {
result.append(obj)
}
}
return result
}
and then call it
self.sortedValues = sortItems()
You don't use sortedKeys at all and the result of sorting a dictionary is an array of tuples so the dictionary enumeration syntax for (key, value) in is wrong
Probably you want this
func sortItems() {
let sortedKeys = self.dictionary.keys.sorted(by: >)
for key in sortedKeys {
let value = self.dictionary[key]!
print(value)
self.sortedValues.append(value)
}
}
Force unwrapping the value is 100% safe as the key clearly exists.

How to retrieve value from all keys with the same name from an array of dictionaries

I would like to retrieve the values from keys named "termKey" from all dictionaries in an array of dictionaries (as I want to display the values in a UITableView). Any suggestions?
Here's the array of dictionaries:
{
"questionData": [
{
"termKey": "respiration"
},
{
"termKey": "mammals"
}
]
}
This is the flattened array:
[(key: "termKey", value: "respiration"), (key: "termKey", value: "mammals")]
The output I want would be something like: ["respiration", "mammals"]
let array = [(key: "termKey", value: "respiration"), (key: "termKey", value: "mammals")]
array.map({ $0.value })
And you will get an array of the values that looks like:
["respiration", "mammals"]
Use compactMap on the array and lookup the dictionary key in the closure:
let questionData = [["termKey": "respiration"], ["termKey": "mammals"], ["badKey": "foo"]]
let values = questionData.compactMap { $0["termKey"] }
print(values)
["respiration", "mammals"]
compactMap runs its closure for each element in the array to create a new array. Here, we look up the value for the key "termKey". Dictionary lookups return an optional value. If the key is not present, the result will be nil. compactMap skips the nil values and unwraps the values that are present.
Decode the JSON into structs and map the result to the termKey values of questionData.
struct Response: Decodable {
let questionData : [Question]
}
struct Question: Decodable {
let termKey : String
}
let jsonString = """
{"questionData": [{"termKey": "respiration"},{"termKey": "mammals"}]}
"""
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Response.self, from: data)
let termKeys = result.questionData.map{$0.termKey}
} catch { print(error) }

Extracting elements from a array of dictionaries

I have a array of dictionaries. From here I want to extract individual elements
The following code is generating an array which has multiple dictionaries. From this I need to extract values which match a certain key.
Code used:
return array.filter{namePredicate.evaluate(with: $0)}
This looks like:
[["a":"1","b":"2","c":3],["a":"3","b":"4","c":5]]
From this I need to extract values for key "a" ie 1, 3. How do I go about this?
Use compactMap:
let aValues = filteredArray.compactMap { $0["a"] }
where filteredArray is the array returned from array.filter{namePredicate.evaluate(with: $0)}.
please informed that, the filter will return the same type as the array it self, the map will return the new type you mean to return. so if you want to get the different type with it self, you need to use map function.
and in map functions "map" will return the same number of elements as the array it self, the "compactMap" will remove the 'nil' value.
so if you make sure all the 'dict' in your array have the key you need to get, you can use map, or you can you use compactMap to avoid nil value in the result array
so you can use
let arr = [["a":"1","b":"2","c":3],["a":"3","b":"4","c":5]]
let test = arr.map{$0["a"] as? String}
let test2 = arr.compactMap{$0["a"] as? String}
If you need to do with for multiple keys, you can make a merged dictionary that maps all keys to arrays of all values. You lose the ordering of the values, so this will only work if it's not necessary.
func merge<Key, Value>(dicts: [[Key: Value]]) -> [Key: [Value]] {
return dicts.reduce(into: [:]) { accumalatorDict, dict in
accumalatorDict.merge(
dict.mapValues({ [$0] }),
uniquingKeysWith: { return $0 + $1 }
)
}
}
let dicts: [[String: Any]] = [
["a":"1","b":"2","c":3],
["a":"3","b":"4","c":5]
]
let mergedDicts = merge(dicts: dicts)
for (key, values) in mergedDicts {
print(key, values)
}
let allValuesForA = mergedDicts["a"]
print(allValuesForA) // => ["1", "3"]
try this
let arr = [["a":"1","b":"2","c":3],["a":"3","b":"4","c":5]]
let test = arr.map{$0["a"] as? String}

Sort an array of dictionaries by a certain key value (Swift 3)

I was looking for a way to sort an array of invoice dictionaries based on invoice ID (which is a value of one of the keys in the dictionary). I couldn't find a solution that worked as well as I would've liked so I wrote out a function which works well for me and I thought I'd share it with the community.
It's pretty simple, you just pass in the array, pass in the key to sort it by and it returns the array sorted.
let testArray: Array<Dictionary<String, String>> = sortDictArrayByKey(arr: someUnsortedArray, key: keyToSort)
func sortDictArrayByKey(arr: Array<Dictionary<String, String>>, key: String) -> Array<Dictionary<String, String>>{
var keyArray = Array<String>()
var usedKeys = Array<String>()
for object in arr{
keyArray.append(object[key]!)
}
keyArray.sort(by: <)
var newArray = Array<Dictionary<String, String>>()
for keyVal in keyArray{
// Check if we've already seen this entry, if so, skip it
if usedKeys.contains(keyVal){
continue
}
usedKeys.append(keyVal)
// Check for duplicate entries
var tempArray = Array<Dictionary<String, String>>()
for object in arr{
if object[key] == keyVal{
tempArray.append(object)
}
}
for item in tempArray{
newArray.append(item)
}
tempArray.removeAll()
}
return newArray
}
Extension
You can created an extension available for Arrays of Dictionaries where both the Key and the Value are String(s).
extension Array where Element == [String:String] {
func sorted(by key: String) -> [[String:String]] {
return sorted { $0[key] ?? "" < $1[key] ?? "" }
}
}
Example
let crew = [
["Name":"Spook"],
["Name":"McCoy"],
["Name":"Kirk"]
]
crew.sorted(by: "Name")
// [["Name": "Kirk"], ["Name": "McCoy"], ["Name": "Spook"]]

Find unique values in a Swift Array

I am building a project that tells me the unique words in a piece of text.
I have my orginal string scriptTextView which I have added each word into the array scriptEachWordInArray
I would now like to create an array called scriptUniqueWords which only includes words that appear once (in other words are unique) in scriptEachWordInArray
So I'd like my scriptUniqueWords array to equal = ["Silent","Holy"] as a result.
I don't want to create an array without duplicates but an array that has only values that appeared once in the first place.
var scriptTextView = "Silent Night Holy Night"
var scriptEachWordInArray = ["Silent", "night", "Holy", "night"]
var scriptUniqueWords = [String]()
for i in 0..<scriptEachWordInArray.count {
if scriptTextView.components(separatedBy: "\(scriptEachWordInArray[i]) ").count == 1 {
scriptUniqueWords.append(scriptEachWordInArray[i])
print("Unique word \(scriptEachWordInArray[i])")}
}
You can use NSCountedSet
let text = "Silent Night Holy Night"
let words = text.lowercased().components(separatedBy: " ")
let countedSet = NSCountedSet(array: words)
let singleOccurrencies = countedSet.filter { countedSet.count(for: $0) == 1 }.flatMap { $0 as? String }
Now singleOccurrencies contains ["holy", "silent"]
Swift
lets try It.
let array = ["1", "1", "2", "2", "3", "3"]
let unique = Array(Set(array))
// ["1", "2", "3"]
Filtering out unique words without preserving order
As another alternative to NSCountedSet, you could use a dictionary to count the the number of occurrences of each word, and filter out those that only occur once:
let scriptEachWordInArray = ["Silent", "night", "Holy", "night"]
var freqs: [String: Int] = [:]
scriptEachWordInArray.forEach { freqs[$0] = (freqs[$0] ?? 0) + 1 }
let scriptUniqueWords = freqs.flatMap { $0.1 == 1 ? $0.0 : nil }
print(scriptUniqueWords) // ["Holy", "Silent"]
This solution, however (as well as the one using NSCountedSet), will not preserve the order of the original array, since a dictionary as well as NSCountedSet is an unordered collection.
Filtering out unique words while preserving order
If you'd like to preserve the order from the original array (removing element which appear more than once), you could count the frequencies of each word, but store it in a (String, Int) tuple array rather than a dictionary.
Making use of the Collection extension from this Q&A
extension Collection where Iterator.Element: Hashable {
var frequencies: [(Iterator.Element, Int)] {
var seen: [Iterator.Element: Int] = [:]
var frequencies: [(Iterator.Element, Int)] = []
forEach {
if let idx = seen[$0] {
frequencies[idx].1 += 1
}
else {
seen[$0] = frequencies.count
frequencies.append(($0, 1))
}
}
return frequencies
}
}
// or, briefer but worse at showing intent
extension Collection where Iterator.Element: Hashable {
var frequencies: [(Iterator.Element, Int)] {
var seen: [Iterator.Element: Int] = [:]
var frequencies: [(Iterator.Element, Int)] = []
for elem in self {
seen[elem].map { frequencies[$0].1 += 1 } ?? {
seen[elem] = frequencies.count
return frequencies.append((elem, 1))
}()
}
return frequencies
}
}
... you may filter out the unique words of your array (while preserving order) as
let scriptUniqueWords = scriptEachWordInArray.frequencies
.flatMap { $0.1 == 1 ? $0.0 : nil }
print(scriptUniqueWords) // ["Silent", "Holy"]
you can filter the values that are already contained in the array:
let newArray = array.filter { !array.contains($0) }

Resources