Method chaining extracts total count of occurrences of a specific value from multiple nested dictionaries

I am trying to extract the number of times a specific string value occurs in a group of nested arrays and would like to use something more concise than an if let loop. Using the filter method I am able to export a dictionary from each of the nested dictionaries with just the string value I am looking for, but I can’t collate the vales to count them all:

var numberOfBananas: [[String] : Int] = [:]
var weekdayFruits: [String : [[String] : Int]] = ["Monday" : [["Orange", "Banana"] : 1], "Tuesday" : [["Banana"] : 2], "Wednesday" : [["Orange", "Apple"] : 1], "Thursday" : [["Grapes", "Peach"] : 12], "Friday" : [["Apple", "Orange"] : 1]]

for (_, value) in weekdayFruits {
        numberOfBananas = value.filter { $0.key.contains("Banana")}

//[["Banana"]: 2]
//[["Orange", "Banana"]: 1]

Ideally I wanted to chain methods just to extract the number of occurrences and have everything on a single line but got the error: Ambiguous reference to member 'filter'

numberOfBananas = value.filter { $0.key.contains("Banana")}.index(forKey: [“Banana”].count)

Your goal is somewhat underspecified.

  • You want to count the fruit array, regardless of what day it is?
  • Each key found will have weight with corresponding value?
    If ["Banana"] : 3 is in there you will count it three times?
  • What if key have multiple "Banana" like ["Banana", "Banana"], do we count them once, twice, or it's impossible so just ignore this case?

If your answer is yes, yes, once/impossible, then this will do.

for (_, value) in weekdayFruits {
    numberOfBananas.merge(value.filter { $0.key.contains("Banana") }, uniquingKeysWith: +)

With a somewhat less cumbersome (but more cryptic?) version

Dictionary(weekdayFruits.flatMap { $0.value }.filter { $0.key.contains("Banana") }, uniquingKeysWith: +)

I couldn't get the ambiguous warning, but I suppose it's from different Swift version. Nevertheless, your code will override the numberOfBananas every time the loop execute. You'll want to aggregate them. That's why I use merge, and aggregate them by adding the value together (with uniquingKeyWith).

The second solution does very similar thing. It removes all the key information (with flatMap), then remove ones that doesn't match "Banana" (with filter). Any duplicate keys will be merged with uniquingKeyWith, you'll want to get the sum of both occurrence, so I use +.

Note that if you will count it a low with different target (other than "Banana"), it's probably better to count it once and then filter unmatched ones as needed.

let counted = Dictionary(weekdayFruits.flatMap { $0.value }, uniquingKeysWith: +)

let bananaCount = counted.filter { $0.keys.contains("Banana") }

Hello again Lantua, I originally wanted to count the fruit arrays for all occurrences throughout the week, but the number value was irrelevant, I just wanted to know the total number of times the same string value occurred throughout the week.

In the end I found the nested dictionary too much to deal with, so I found a way to have an array of arrays [String : [Enum]] and then iterated over them and added all occurrences to variable:

bananasEaten += value.filter { $0.rawValue.contains(Breakfast.banana.rawValue) }.count

It's a simpler way, and I should take note for nested dictionary filtering and such in the future. So many thanks for your help!

By the way, in your experience, are nested dictionaries used a lot and filtered?

I don't personally nest Dictionary a lot. Though it does make sense at times.

In any case, Dictionary is used when there's a mapping from Key to Value that make sense, duh. But really, if you ever ask "I got a single A, how do I get the corresponding B?", then [A: B] is probably the best way to do it, otherwise you can just use something else. I stress on a single part because otherwise there can be a lot of other options.

If you always work with every pair of (A, B), like the counting you just did, you can generally use something else such as [(A, B)] or even a pair of parallel [A], [B].