Extract values of dictionary - swift

struct Country {
var translations : [String:String?]? // must be defined as optionals!
}
// example entry
translationsDict = [
"translations": [
"de":"Deutschland",
"en": "germany",
"it": nil
]
]
How can I extract the values "Deutschland" and "germany" into a new array?
result should be:
["germany", "Deutschland"]

Firstly, get a collection of all values of translations, then convert it to an array.
if let collection = translationsDict["translations"]?.values {
let array = Array(collection)
print(array)
}
or
if let array = translationsDict["translations"]?.map({ $0.1 }) {
print(array)
}

Related

Swift: how to reduce some sub-elements in a Set with a one-liner?

I have this model:
struct Class {
var field: [String: Field]
}
struct Field {
var type: String
}
And this array:
let classes: [Class] = [
Class(field: ["test": Field(type: "STRING"),
"test2": Field(type: "STRING"),
"test3": Field(type: "NUMBER")]),
Class(field: ["test": Field(type: "POINTER"),
"test2": Field(type: "STRING"),
"test3": Field(type: "STRING")]),
]
I would like to reduce all the types properties in a Set of Strings, I tried this:
let result = classes.map { $0.field.reduce([], { $0 + $1.value.type }) }
But instead of getting a set of strings:
What I would like to get
"STRING", "NUMBER", "POINTER"
I get an array of characters:
[["S", "T", "R", "I", "N", "G", "N", "U", "M", "B"....]]
What should I write instead? Thank you for your help
You can use flatMap to flatten the arrays of values and then use Set to get rid of non-unique values:
let result = Set(classes.flatMap { $0.field.values }.map { $0.type })
If you need an Array instead of a Set, you can simply wrap the above in Array()

Looping through dictionary to find value

Usually I just step into the dictionary and find the value once inside. In this case, I need to step in, loop through to find a value then step back out again then find a different value. Example:
address_components": [
{
"long_name": "E3 2AA",
"short_name": "E3 2AA",
"types": [
"postal_code"
]
},
{
"long_name": "London",
"short_name": "London",
"types": [
"postal_town"
]
}
]
This is what googles api dictionary looks like and I need to fetch the long_name of the postal_town.
You can use the first(where: function passing a closure to filter for the type, this avoids a loop:
if let addressComponents = json["address_components"] as? [[String:Any]],
let postalTownComponent = addressComponents.first(where: { ($0["types"] as! [String]).contains("postal_town") }) {
print(postalTownComponent["long_name"])
}
This should do it for you:
let result = addressComponents.filter({ return ($0["types"] as! [String]).contains("postal_town") }).map({ $0["long_name"] })
address_components is an array so you can just loop over it and check the value of your key. Maybe something like:
var names = []
for entry in addressComponent {
if addressComponent["types"][0] == "postal_town" {
names.append(addressComponent["long_name"])
}
}
Should it just that simple, or I misunderstand your question? An example:
let dict = [ "long_name": "E3 2AA", "short_name": "E3 2AA"]
for (key, value) in dict {
print("your key: \(key) and value: \(value)")
}

How to dynamically build mongodb query

I have a match expression in a mongodb aggregation. There are 3 fields that are included in the match but they don't all always contain data. I only want to include the fields in the match if the field isn't empty.
This is what the match looks like if all fields have data but for example, if the array used for studentGradeLevels is empty, then I don't want to include it or I want the query to still return data ignoring the empty parameter.
$match: {
"school._id": "7011",
"studentGradeLevels": { $in: ["09", "10", "11", "12"] },
"contentArea": {
$in: [
"English 1"
]
}
}
Is there a way to either dynamically build the match so that I only include the fields I want based on if they are empty or not or do something in the query so that parameters that are empty are ignored.
You can use $in when the array is not empty and $nin when the array is empty, this way the match field will not be taken into account ($nin : []):
function buildMatch(arr) {
var matcher = {};
if (arr.length == 0)
matcher["$nin"] = arr;
else
matcher["$in"] = arr;
return matcher;
}
var grades = ["09", "10", "11", "12"];
var areas = [ "English 2" ];
var gradeMatch = buildMatch(grades);
var areaMatch = buildMatch(areas);
db.students.aggregate([{
$match: {
"school._id": "7011",
"studentGradeLevels": gradeMatch,
"contentArea": areaMatch
}
}])
A variation: Check fields for existence and if so, change to a special value e.g. _ and add that to the $inlist:
db.foo.aggregate([
{$project: {"studentGradeLevels":{$ifNull: ["$studentGradeLevels","_"]},
"contentArea":{$ifNull: ["$contentArea","_"]}
}}
,{$match: {
"studentGradeLevels": { $in: ["_", "09", "10", "11", "12"] },
"contentArea": { $in: [ "_", "English 1" ] }
}}
]);

Add new entries to a "complex" dictionary

I have a more "complex" dictionary that I am trying to add new entries to. The dictionary code is as follows:
var users: [[String:Any]] = [
[
"firstName": "Bill",
"lastName": "G"
]
]
I tried adding a new entry with this code:
users[1]["firstName"] = "Steve"
users[1]["lastName"] = "J"
print(users) // fatal error: Array index out of range
But receive "fatal error: Array index out of range" I looked at several examples but all seem to deal with simpler dictionaries. What am I missing?
You need to first create an instance of the dictionary and add it to the array, you can then add key:value pairs to it:
var users: [[String:Any]] = [
[
"firstName": "Bill",
"lastName": "G"
]
]
users.append([String:Any]())
users[1]["firstName"] = "Steve"
users[1]["lastName"] = "J"
print(users) // [["firstName": "Bill", "lastName": "G"], ["firstName": "Steve", "lastName": "J"]]
You can even do it in a single statement like this:
users.append(["firstName": "Steve", "lastName": "J"])
If you don't append a new element on to the array then when you try to reference the next index it will be out of range.
This works in a playground
var users: [[String:Any]] = [
[
"firstName": "Bill",
"lastName": "G"
]
]
users[0]["firstName"] = "Steve"
users[0]["lastName"] = "J"
users
Using index 0, not 1 like you were using which causes the index out of bounds error
Or the more dynamic approach
var users = [[String:AnyObject]]()
let personA = ["Steve":"J"]
let personB = ["Foo":"Z"]
users.append(personA)
users.append(personB)
users

Filtering nested lists inside a dictionary in Swift

I've got an object in Swift that is a dictionary of type Dictionary<String, String[]>. I'd like to be able to filter the String[] array whilst maintaining the dictionary structure.
let list: Dictionary<String, String[]> = [
"Vegetables" : [ "Carrot", "Potato" ],
"Fruit" : [ "Apple", "Orange", "Banana" ]
]
I'd like to be able to filter for everything containing an "O", and end up with something that looks like this:
[
"Vegetables" : [ "Carrot", "Potato" ],
"Fruit" : [ "Orange" ]
]
To filter the arrays, I've been doing this:
["Carrot", "Potato"].filter { ($0 as NSString).containsString("o") }
However, the part I'm struggling with now is mapping over the dictionary - because then I can preserve the key and call that filter function on the value. How would I go about doing this? Thanks in advance!
You can do it in a for in loop:
for (key, array) in list {
list[key] = array.filter { ($0 as NSString).containsString("o") }
}
You can also add your own map method to Dictionary:
extension Dictionary {
func map(f: (KeyType, ValueType) -> ValueType) -> [KeyType:ValueType] {
var ret = [KeyType:ValueType]()
for (key, value) in self {
ret[key] = f(key, value)
}
return ret
}
}
Then you can simply do:
var filteredList = list.map { $1.filter { ($0 as NSString).containsString("o") } }
Note: My implementation of map on Dictionary returns a copy of the dictionary to be more like the map method of Array
You can enumerate it and filter in 1 go. I just made list a variable instead of a const to make it simpler. Here is the code:
//it's a variable
var list: Dictionary<String, String[]> = [
"Vegetables" : [ "Carrot", "Potato" ],
"Fruit" : [ "Apple", "Orange", "Banana" ]
]
for (key, array) in list {
list[key] = array.filter({
(x : String) -> Bool in
return !(x.rangeOfString("o", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil, locale: nil).isEmpty)
})
}
NSLog("%#", list)
The output will be :
{
Fruit = (
Orange
);
Vegetables = (
Carrot,
Potato
);
}