GoForIt
GoForIt

Reputation: 49

Filter a dictionary with a nested dictionary in Swift 5

I have a dictionary that contains a nested dictionary as the value. What I am trying to accomplish is search the nested dictionary and add those matching values to a variable called "FilterData".

var Data: [Int : [String : String]] = [:]
var filteredData: [Int : [String : String]] = [:]
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %@", searchController.searchBar.text!)

//Don't know how to filter the nested dictionary, I can filter an array.
let array = (Data as NSArray).filtered(using: searchPredicate)
filteredData = array as! [String]

Does anyone have an idea as to how I should approach this?

Upvotes: 0

Views: 1447

Answers (2)

Cristik
Cristik

Reputation: 32785

Firstly, you don't need NSPredicate for such a simple task, String.contains() should work as well.

Secondly, you can use a combination of mapValues and compactMapValues to achieve the goal:

let searchQuery = "foo"
var data: [Int : [String : String]] = [:]
// map the values of the outer dictionary
let filteredData = data.mapValues {
    // we use compactMapValues on the inner dictionary as we want to exclude the values 
    // that don't match our search
    $0.compactMapValues { $0.contains(searchQuery) ? $0 : nil }
}

If you want to further refine the search, for example to not include the Int keys that don't have any matches in the inner dictionary, you can compactMapValues on the outer dictionary too:

let filteredData = data.compactMapValues {
    let filteredDict = $0.compactMapValues { $0.contains(searchQuery) ? $0 : nil }
    return filteredDict.isEmpty ? nil: filteredDict
}

Upvotes: 1

Aaron Cyrman
Aaron Cyrman

Reputation: 556

To give you some examples how to filter I created a set of data from the winner and runner up in the Champions League tournament. In the main dictionary the Int key correspond to the tournament's year. The dictionary inside has two keys: 1stPlace and 2ndPlace. So for example in 2018 Real Madrid defeated Liverpool, the dictionary will look like this:

2018: ["1stPlace": "Real Madrid",   "2ndPlace": "Liverpool"]

Assuming this structure the Data variable has the results from 2018 to 2011. If you want to know who won in 2018 you can do something like this:

filteredData = Data.filter { $0.key == 2018 }

If you want know when Real Madrid won you doo like this:

filteredData = Data.filter { $0.value["1stPlace"] == "Real Madrid" }

If you want to know when Real Madrid defeated Atlético Madrid you do it like this:

filteredData = Data.filter {
  $0.value["2ndPlace"] == "Atlético Madrid" &&
  $0.value["1stPlace"] == "Real Madrid"
}

When you have the results in filteredData you can do query the data in different ways: you can sort by year, you can extract just the years, etc.

The following code is the playground I used to test different kinds of queries:

import Foundation

typealias DictOfDicts = [Int : [String : String]]

var Data: DictOfDicts = [
  2018:
    ["1stPlace": "Real Madrid",   "2ndPlace": "Liverpool"],
  2017:
    ["1stPlace": "Real Madrid",   "2ndPlace": "Juventus"],
  2016:
    ["1stPlace": "Real Madrid",   "2ndPlace": "Atlético Madrid"],
  2015:
    ["1stPlace": "Barcelona",     "2ndPlace": "Juventus"],
  2014:
    ["1stPlace": "Real Madrid",   "2ndPlace": "Atlético Madrid"],
  2013:
    ["1stPlace": "Bayern Munich", "2ndPlace": "Borussia Dortmund"],
  2012:
    ["1stPlace": "Chelsea",       "2ndPlace": "Bayern Munich"],
  2011:
    ["1stPlace": "Barcelona",     "2ndPlace": "Manchester United"]
  ]

var filteredData: DictOfDicts = [:]

print("Results from 2017 to present")
print("----------------------------")
filteredData = Data.filter { $0.key >= 2017 } // Filter from 2017 to present
filteredData
  .sorted { $0.key > $1.key } // Sort by year
  .forEach {
    print("In \($0.key) \($0.value["1stPlace"]!) defeated \($0.value["2ndPlace"]!)")
  }
print("")

print("Results before 2015")
print("----------------------------")
filteredData = Data.filter { $0.key < 2015 } // Filter before 2015
filteredData
  .sorted { $0.key > $1.key } // Sort by year
  .forEach {
    print("In \($0.key) \($0.value["1stPlace"]!) defeated \($0.value["2ndPlace"]!)")
  }
print("")


filteredData = Data.filter { $0.value["1stPlace"] == "Real Madrid" } // Filter Real Madrid won
var years = filteredData
  .sorted { $0.key < $1.key } // Sort by year
  .map { "\($0.key)" } // Convert year to string
  .joined(separator: ", ") // Convert to comma separated single string
print("Real Madrid won in: \(years)")
print("")

filteredData = Data.filter { $0.value["2ndPlace"] == "Juventus" } // Filter Juventus lost
years = filteredData
  .sorted { $0.key > $1.key } // Sort by year
  .map { "\($0.key)" } // Convert year to string
  .joined(separator: ", ") // Convert to comma separated single string
print("Juventus lost the final match in: \(years)")
print("")

filteredData = Data.filter {
  $0.value["2ndPlace"] == "Atlético Madrid" &&
  $0.value["1stPlace"] == "Real Madrid"
} // Filter Real Madrid defeated Atlético Madrid
years = filteredData
  .sorted { $0.key > $1.key } // Sort by year
  .map { "\($0.key)" } // Convert year to string
  .joined(separator: ", ") // Convert to comma separated single string
print("Real Madrid defeated Atlético Madrid in: \(years)")
print()

let winnersAndChampionships =
  Set(Data.map { $0.value["1stPlace"]! }) // Get winners' names
  .map { winner in
    (winner, Data.filter { $0.value["1stPlace"] == winner }.count)
  } // Map for each winner the number of wins
  .sorted { $0.1 > $1.1}
print("Number of Champions League's wins per team")
print("------------------------------------------")
winnersAndChampionships.forEach {
  print("\($0.0): \($0.1)")
}

The playground's output is:

Results from 2017 to present
----------------------------
In 2018 Real Madrid defeated Liverpool
In 2017 Real Madrid defeated Juventus

Results before 2015
----------------------------
In 2014 Real Madrid defeated Atl&eacute;tico Madrid
In 2013 Bayern Munich defeated Borussia Dortmund
In 2012 Chelsea defeated Bayern Munich
In 2011 Barcelona defeated Manchester United

Real Madrid won in: 2014, 2016, 2017, 2018

Juventus lost the final match in: 2017, 2015

Real Madrid defeated Atl&eacute;tico Madrid in: 2016, 2014

Number of Champions League's wins per team
------------------------------------------
Real Madrid: 4
Barcelona: 2
Bayern Munich: 1
Chelsea: 1

Upvotes: 1

Related Questions