Obtain a subset of tuple array in swift - arrays

I have a array of tuples like the following
var customProducts = [(productType: String, info:[String:AnyObject?])]
The parameter "productType" works like a "product category", like fruits, grains, beverage, etc.
The parameter "info" is a dictionary of nutritional information of the product.
I want to get a subset of the tuples array, based on the productType so I could obtain just the "info" dictionary for an specific productType. In C# I would try something like the following using Linq:
var fruits = customProducts.Where(q=>q.productType == "fruit").Select(q => q.info) as List<KeyValuePair<string, object>>;
How may I archive the same results using Swift (2.x)?
Thanks!

I think the Swift equivalent would be:
let fruits = customProducts.filter { $0.productType == "fruit" }.map { $0.info }
Here fruits is [[String : AnyObject?]], an array of dictionaries (an array of info, the same as your List<KeyValuePair<string, object>> if I'm not mistaken).

You can use the filter method
let beverageInfo = (customProducts.filter { $0.productType == "Beverage" }).first?.info
Now beverageInfo is [String : AnyObject?]?, an optional dictionary representing the info for "Beverage" tuple.

You should filter the array according to what type the product is
Eg
var customProducts = [(productType: String, info:[String:AnyObject?])]
let fruitProducts = customProducts.filter { product in
if product.productType == "fruit" {
return true
} else {
return false
}
}.map { $0.info }
Then you can use fruitProducts however you want.

Related

How to get all tuples with specific value in swift

Say I have an array of tuples like this:
var countryData = [
(country:"Australia", item:"GDP", Year:"2019", dataValue:"1.434 trillion"),
(country:"Australia", item:"CPI", Year:"2019", dataValue:"6401.0"),
(country:"Australia", item:"Inflation", Year:"2019", dataValue:"1.61%"),
(country:"Brazil", item:"GDP", Year:"2019", dataValue:"$1.868 trillion"),
...
(country:"Zimbabwe", item:"Inflation", Year:"2019", dataValue:"255.29%"),
]
I want to create an instance variable to get all the tuples containing "Australia". I'm assuming I have to use a for loop and contain function but my swift isn't that good and I can't get it to work. Or any suggestions if this isn't the best way to go about this is also appreciated.
You are right! You can use for loop for this. The other way is the filter operator
filter operator way gives you an ability to filter an array of any type by specific criteria.
let neededList = countryData.filter { $0.country == "Australia" }
For loop way
var result: [(country: String, item: String, Year: String, dataValue: String)] = []
for item in countryData {
if item.country == "Australia" {
result.append(item)
}
}
print(result)
Use the higher order filter method. Here's how:
let australiaData = countryData.filter { $0.country == "Australia" }

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

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}

How to group Array of Dictionaries by a key in swift?

For example, I have this array of dictionaries
[["Country":"Egypt","Name":"Mustafa","Age":"20"],["Country":"Palestine","Name":"Omar","Age":"15"],["Country":"Egypt","Name":"Ali","Age":"40"],["Country":"Jordan","Name":"Ahmad","Age":"25"],["Country":"Palestine","Name":"Amani","Age":"30"],["Country":"Jordan","Name":"Mustafa","Age":"20"]]
I want to group them by Country to become
{"Egypt": [{"Country":"Egypt","Name":"Mustafa","Age":"20"} {"Country":"Egypt","Name":"Ali","Age":"40"}],
"Palestine": [{"Country":"Palestine","Name":"Amani","Age":"30"},{"Country":"Palestine","Name":"Omar","Age":"15"}],
"Jordan":[{"Country":"Jordan","Name":"Ahmad","Age":"25"},{"Country":"Jordan","Name":"Mustafa","Age":"20"}]
}
Please help.
Swift has a nice function that does this for you...
let people = [["Country":"Egypt","Name":"Mustafa","Age":"20"],["Country":"Palestine","Name":"Omar","Age":"15"],["Country":"Egypt","Name":"Ali","Age":"40"],["Country":"Jordan","Name":"Ahmad","Age":"25"],["Country":"Palestine","Name":"Amani","Age":"30"],["Country":"Jordan","Name":"Mustafa","Age":"20"]]
let peopleByCountry = Dictionary(grouping: people, by: { $0["Country"]! } )
peopleByCountry will now be the format that you want.
You can read more about this function in the documentation.
Just to add to Hamish's comment.
You really shouldn't be working with Dictionaries here. You should be working with Structs...
struct Person {
let countryName: String
let name: String
let age: Int
}
Even better would be to have a Country struct...
struct Country {
let name: String
}
and use that in the Person for their country property instead of String.
let arrCountry: [[String:String]] = [["Country":"Egypt","Name":"Mustafa","Age":"20"],
["Country":"Palestine","Name":"Omar","Age":"15"],
["Country":"Egypt","Name":"Ali","Age":"40"],
["Country":"Jordan","Name":"Ahmad","Age":"25"],
["Country":"Palestine","Name":"Amani","Age":"30"],
["Country":"Jordan","Name":"Mustafa","Age":"20"]]
func sortCountry() {
var sortedCountries : [String : [[String:String]]] = [:]
for object in arrCountry {
let country = object["Country"] as! String
if var arrCountry = sortedCountries[country] {
arrCountry.append(object)
sortedCountries[country] = arrCountry
}
else {
sortedCountries[country] = [object]
}
}
}
Well I would go like this:
Get all the countries by traversing the array once and store it in an array.
Loop for this array of countries.
Filter the array with predicate where country is current country.
Store this in the final dictionary of country.

How to filter an array to correspond other array

I've two arrays:
var filteredTitles = [String]()
var filteredTypes = [String]()
I filter the first array as a part of using searchbar. The order of the elements might change completely. However, I can't filter the second array the same way I did the first one, because I don't want to take it in to count when searching. But I would like for the second array to be in the same order as the first one. So, to recap. How can I filter an array to match another one by indexes perfectly?
An example:
var filteredArray = ["One", "Two", "Three"]
//Sort the below array to ["1", "2", "3"], the order of the upper array
var toBeFilteredArray = ["2", "1", "3"]
WITHOUT using alphabetical or numerical order, as that won't do in this case.
EDIT:
TO Russell:
How do I sort the titles like this:
// When there is no text, filteredData is the same as the original data
// When user has entered text into the search box
// Use the filter method to iterate over all items in the data array
// For each item, return true if the item should be included and false if the
// item should NOT be included
searchActive = true
filteredData = searchText.isEmpty ? original : original.filter({(dataString: String) -> Bool in
// If dataItem matches the searchText, return true to include it
return dataString.range(of: searchText, options: .caseInsensitive) != nil
})
don't have two arrays - have a single array of a custom type, containing both variables that you need
Define your struct
struct MyCustomData
{
var dataTitle : String = ""
var dataType : String = ""
}
and then declare it
var dataArray : [MyCustomData] = []
populate it and sort it where required - I have populated in reverse order just so that we can see it being sorted
dataArray.append(MyCustomData(dataTitle: "Third", dataType: "3"))
dataArray.append(MyCustomData(dataTitle: "Second", dataType: "2"))
dataArray.append(MyCustomData(dataTitle: "First", dataType: "1"))
let filteredArray = dataArray.sorted {$0.dataTitle < $1.dataTitle}
for filteredElement in filteredArray
{
print("\(filteredElement.dataTitle), \(filteredElement.dataType)")
}
// or, to print a specific entry
print("\(filteredArray[0].dataTitle), \(filteredArray[0].dataType)")
An example of keeping two separate arrays in sync using zip:
let titles = ["title1", "title3", "title4", "title2"]
let types = ["typeA", "typeB", "typeC", "typeD"]
let zipped = zip(titles, types)
// prints [("title4", "typeC"), ("title2", "typeD")]
print(zipped.filter { Int(String($0.0.characters.last!))! % 2 == 0 })
You can use map on the filtered result to get back two separate filtered arrays for the titles and types.

Resources