Appending dictionary values to array in Swift - arrays

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.

Related

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}

Add Parse array to dictionary swift

I have some objects in parse and I am getting the data successfully as [PFObjects]. The issue is that I am trying to add the array elements [PFObjects] to a dictionary as values. But I keep getting an empty dictionary, so the values are not added to the dictionary. The dictionary count is also 0.
This is what I tried so far:
var postDictionary = [String:[AnyObject]]()
query.findObjectsInBackground(block: { (posts: [PFObject]?, error:Error?) in
if let unwrappedPosts = posts {
for posts in unwrappedPosts {
if let postText = posts.object(forKey: "title") as?String {
self.titleArray.append(postText)
print("count", self.titleArray.count) // count 10
self.postDictionary["title"]?.append(self.titleArray as AnyObject)
**try to force unwrap **
self.postDictionary["title"]!.append(self.titleArray as AnyObject), and the app crashed
for (title, text) in self.postDictionary {
print("\(title) = \(text)")
}
print("Dictionay text count",self.postDictionary.count) // count is 0
}
}
}
})
This syntax is very confusing
self.titleArray.append(postText)
self.postDictionary["title"]?.append(self.titleArray as AnyObject)
You append a string to an array and then you are going to append the array to the array in the dictionary. I guess this is not intended.
I recommend to map the title strings and set the array for key title once
var postDictionary = [String:[String]]()
query.findObjectsInBackground(block: { (posts: [PFObject]?, error:Error?) in
if let unwrappedPosts = posts {
self.titleArray = unwrappedPosts.compactMap { $0.object(forKey: "title") as? String }
self.postDictionary["title"] = self.titleArray
for (title, text) in self.postDictionary {
print("\(title) = \(text)")
}
print("Dictionay text count",self.postDictionary.count) // count is 0
}
})
Never use AnyObject if the type is more specific.
The proper way of adding to a dictionary is using updateValue because as far as i can see that you don't have the key "title" in your dictionary and you are appending values to unknown key i guess.
This should help:
titleArray.append(postText)
postDictionary.updateValue(titleArray as [AnyObject], forKey: "title")
for (key,value) in postDictionary {
print("\(key) \(value)")
}
Finally this should print:
title [posts1, posts2, posts3]

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

Swift loop through array of dictionaries

Why am I getting this error when looping through an array of dictionaries ?
import UIKit
let tableView = UITableView()
func meh() {
let product = buildCells()
for (identifier, nib) in product {
tableView.registerNib(nib, forCellReuseIdentifier: identifier)
}
}
func buildCells() -> [[String: UINib]] {
var collector = [[String: UINib]]()
let identifier = "identifier"
let nib = UINib(nibName: "TableViewCell", bundle: nil)
let asdf = [identifier: nib];
collector.append(asdf)
return collector
}
The forin loop in the meh() method produces the following error:
'Dictionary' is not convertible to '([[String : UINib]], [[String : UINib]])'
We can't iterate through the keys and values of an array of dictionaries. We can iterate through the dictionaries though. And for each dictionary, we can iterate through its keys and values:
let product = buildCells()
for dict in product {
for (identifier, nib) in dict {
tableview.registerNib(nib, forCellReuseIdentifier: identifier)
}
}
But I think the problem is actually more likely the nonsense going on in buildCells(). Why are you returning an array of dictionaries? Do you plan to have duplicate keys? I don't think the tableview will let you register multiple nibs for the same identifier.
Why don't we just return a dictionary?
func buildCells() -> [String: UINib] {
var dict = [String: UINib]()
dict["identifier"] = UINib(nibName: "TableViewCell", bundle: nil)
// rinse & repeat for all of your other ID/nib combos with no duplicate IDs
return dict
}
Now we can iterate over the key/value pairs in the dictionary without the outer loop:
for (identifier, nib) in dict {
tableview.registerNib(nib, forCellReuseIdentifier: identifier)
}

Resources