Array of dictionaries. Get the values of a specific key

var arrayDictionaries: [[String: Any]] = [["key1": 1.1, "key2": 2.1, "key3": "value3.1"], ["key1": "value1.2", "key2": "value2.2", "key3": "value3.3"]]

How can I get all the values of a specific key? In this simplified code, how to print all the key3

let allKey3 = arrayDictionaries.compactMap { $0["key3"] }

will give you ["value3.1", "value3.3"].

Would you like me to describe how compactMap works?

2 Likes

Yes! please explain it. It is new for me

Let's start with a map. It takes a function, and keeps applying it for every element, and then returns all of them as an array

let arr = [1, 2, 3]
func multiplyByTwo(_ integer: Int) -> Int {
    return integer * 2
}
print(arr.map(multiplyByTwo)) // [2, 4, 6]

but usually you don't want to make a function just to pass it to map, so usually we use a closure for that. $0 here means the (first) argument to the closure.

let arr = [1, 2, 3]
print(arr.map { $0 * 2 }) // [2, 4, 6]

So let's try to use map to get all all key3 from the array of dictionaries (I added a third dictionary to better make my point)

var arrayDictionaries: [[String: Any]] = [["key1": 1.1, "key2": 2.1, "key3": "value3.1"], ["key1": "value1.2", "key2": "value2.2", "key3": "value3.3"], ["key1": "value1.3"]]
print(arrayDictionaries.map { $0["key3"] }) // [Optional("value3.1"), Optional("value3.3"), nil]

It does what we want, which is to extract all key3, but everything is optional and there is a nil. To get rid of that, we use compactMap, which is just like map, but skips the nils afterwards. Notice how there are only two values extracted from three dictionaries

var arrayDictionaries: [[String: Any]] = [["key1": 1.1, "key2": 2.1, "key3": "value3.1"], ["key1": "value1.2", "key2": "value2.2", "key3": "value3.3"], ["key1": "value1.3"]]
print(arrayDictionaries.compactMap { $0["key3"] }) // ["value3.1", "value3.3"]
7 Likes

Very well explained. I understand. I have been many hours with this and you solved in a few minutes.

2 Likes

@cukr Awesome! Question for you, how can I implement this or a similar code with arrays? If you can help it would be most appreciated! I'm trying to grab all of the numbers so I can add them all up for a total sum.

struct Months{
    let day: String
    let number: Int
}

let array = [
    Months(day: "Mon", number: 5),
    Months(day: "Tues", number: 1),
    Months(day: "Fri", number: 2)
]

let allNumbers = array.compactMap { $0["number"] }
// "Value of type 'Months' has no subscripts" -error message
1 Like

In your example you're not trying to access a dictionary by key, you're trying to get a property from a Months - the syntax for that is different

let allNumbers = array.compactMap { $0.number } // [5, 1, 2]

It works, but there's an invisible problem for people reading your code. Remember how I introduced compactMap to get rid of Optionals? Your Months always includes a number, so there's no Optionals in your code. You should use plain map to not confuse people reading your code into thinking that there's something more happening here

let allNumbers = array.map { $0.number } // [5, 1, 2]
2 Likes

Amazing. Haha I'm just as shocked as the first guy you helped lol.
That worked well with the sample problem, but unfortunately it doesn't work as smoothly for my actual problem, BUT I feel like I'm making progress!
I'm using a Realm database, and I'm trying to to access its linked object:

class Months: Object {
@objc dynamic var  dates = ""
let ledger = List<Ledgers>()
}
class Ledgers: Object {
    @objc dynamic var amount: Double = 0.0
    @objc dynamic var date: String = ""
    @objc dynamic var notes: String = ""
    var parentCategory = LinkingObjects(fromType: Months.self, property: "ledger")
}

With this code, I'm able to create separate ledger arrays for the different Months() right... and I'm trying to pull out all of the amounts within a specific Months 's ledger, so I can eventually get a "totalAmount" of a specific Months ' s ledger.

Trying your recommended code for the sample, I get back this mess in my print statement:

LazyMapSequence<List<Ledgers>, Double>(_base: List<Ledgers> <0x6000016ad080> (
	[0] Ledgers {
		amount = 9.99;
		date = 12/01;
		notes = food;
	},
	[1] Ledgers {
		amount = 55.5;
		date = 12/19;
		notes = Bob Ross Outfit;
	}

I don't even know what to do with this haha!

to get here I used these variables & etc..

func tryingToGetSum(){
        if let currentMonth = selectedMonth?.ledger {
            let allNumbers = currentMonth.map { $0.amount}
            print("Here it is: \(allNumbers)")
        }
    }

Thanks for taking the time to even look at this mess!

I think you're on a right track! The mess you're seeing is because Realm uses LazyMapSequence instead of an Array you're familiar with. Thankfully you can convert one to an Array pretty easily by wrapping that into a constructor
Can you tell me what happens if you do

print("Here it is: \(Array(allNumbers))")

instead of your current print? I think you should get the [9.99, 55.5] you're looking for

Then, to get the sum you can use a reduce method

let sum = allNumbers.reduce(0, +)
print(sum) 

reduce works by starting at zero (the first parameter) and then combining it with all the numbers using addition (the second parameter)

I don't have access to Realm right now, so tell me if what I wrote doesn't work

and don't worry! The mess isn't somehow your fault! I just know what's happening because of more experience

1 Like

Honestly bro, I feel so loved haha
It worked!
Thank you for blowing my mind and giving me new study material. I hope and pray one day I get to work next to someone even close to your skill and, more importantly, your willingness to help.

Thank you thank you thank you!

1 Like