Extracting elements from a array of dictionaries - arrays

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}

Related

How to Sort an array of dictionary 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)].

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

How to filter for a specific key's value in an array?

We have json value that returns an array:
"sentMoney": [
{
"amount": 3840.83,
"currency": "MXN",
"isMajor": false
},
{
"amount": 200,
"currency": "USD",
"isMajor": true
}
]
What I'm trying to do is filter it so I can get the value for the "amount" key. This is what I have so far but I get a " has no subscript members" error message:
let filteredItem = self.postTransferSuccess?.sentMoney.filter{$0["sentMoney"]}[0]
self.emailMessage.amount = filteredItem
Any help is appreciated!
Since your sentMoney is key that has an array of dictionary has value, you should do this:
let resultObj = (dict["sentMoney"] as? [[String:Any]])?.filter({ ($0["amount"] as? Double) == someValue }).first // dict should be the name of your dictionary
keep in mind your data should like what is below, so no curly braces:
var dict = ["sentMoney": [
["amount": 3840.83,"currency": "MXN", "isMajor": false],
["amount": 200.0,"currency": "USD","isMajor": true]
]
]
Your second option is to transform your dictionaries into structs, which will become easier to iterate over.
struct Item {
let amount:Double
let currency:String
let isMajor:Bool
init?(_ dict:[String:Any]?) { //<-- failable init will return a nil if there an empty key
guard let _dict = dict,
let amount = _dict["amount"] as? Double,
let currency = _dict["currency"] as? String,
let isMajor = _dict["isMajor"] as? Bool
else { return nil }
self.amount = amount
self.currency = currency
self.isMajor = isMajor
}
}
Now when you need to iterate, you'll do:
var someValue = 200.0
if let arr = dict["sentMoney"] as? [[String:Any]] {
let items = arr.flatMap({ Item($0)}) //<-- will remove any optional item from your list
let singleItem = items.first { $0.amount == someValue }
// or arr.flatMap({ Item($0) }).filter({ $0.amount == someValue}).first
}
(Side Node) Avoid using ! at all cost, use either if let or guard statement to unwrapped your optionals.
I'm not entirely sure what your question is.
.filter isn't the right function here though bc .filter is used to remove unwanted elements of an array/collection. (so you filter with a condition and it'll copy over the value to a new collection only if that condition evaluates to true).
What you might be looking for is .map.
As in:
let amountsOnlyArray = sentMoney.map({ $0["amount"] })
which would give you [3840.43, 200]
If you only want the first item for some reason and want to ignore I THINK you should be able to do
let firstAmountOnly = sentMoney[0]["amount"]
Note that amountsOnlyArray is actually of type [Any] so you'll have to do some casting somewhere.
Note this assumes you have some sort of sentMoney:[String:Any] collection which I'm assuming given your initial code filters sentMoney

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"]]

Resources