Reputation: 7960
This is what I have, an array of dictionary response I fetched from an API:
var response = [
["title": "First one", "code": "the_first", "category": "Entertainment category"],
["title": "Second one", "code": "the_second", "category": "Entertainment category"],
["title": "Third one", "code": "the_third", "category": "News category"],
["title": "Fourth one", "code": "the_fourth", "category": "News category"],
["title": "Fifth one", "code": "the_fifth", "category": "Children category"],
["title": "Sixth one", "code": "the_sixth", "category": "Children category"]
]
What I need to have is:
var channels_by_category = [
["title":"Entertainment category",
"channels":[
["title": "First one", "code": "the_first", "category": "Entertainment category"],
["title": "Second one", "code": "the_second", "category": "Entertainment category"]
]],
["title":"News category",
"channels":[
"title": "Third one", "code": "the_third", "category": "News category"],
["title": "Fourth one", "code": "the_fourth", "category": "News category"]
]]
],
["title":"Children category",
"channels":[
["title": "Fifth one", "code": "the_fifth", "category": "Children category"],
["title": "Sixth one", "code": "the_sixth", "category": "Children category"]
]]
]
In JavaScript world, I'd use lodash to get over this problem. However, I'm not experienced enough in Swift. I guess that it should be some kind of combination of for
loop and reduce
but I couldn't figure out how to do it.
How to achieve what I want to get?
Thank you
Upvotes: 1
Views: 689
Reputation: 59536
Since a good answer to your question has already been provided I am suggesting here a different solution
You should not use dictionaries for your data model. This solutions shows you how to use a struct.
first of all lets define a struct to represent your data model
struct Show {
let title: String
let code: String
let category: String
init?(dict:[String:String]) {
guard let
title = dict["title"],
code = dict["code"],
category = dict["category"]
else { return nil }
self.title = title
self.code = code
self.category = category
}
}
Next let's convert your raw data into a list of Show(s)
let shows = response.flatMap(Show.init)
And finally let's group the list by category
let categories: [String:[Show]] = shows.reduce([String:[Show]]()) { (categories, show) -> [String:[Show]] in
var categories = categories
categories[show.category] = (categories[show.category] ?? []) + [show]
return categories
}
Now categories has your category name as key and a list of Show(s)
as value.
Show(s)
if let shows = categories["News category"] {
for show in shows {
print(show.title)
}
}
or
categories["News category"]?.forEach { show in
print(show.title)
}
for elm in categories {
let category = elm.0
let shows = elm.1
print("Category \(category) has \(shows.count) shows")
shows.forEach { show in
print(" - \(show.title)")
}
}
Result
Category News category has 2 shows
- Third one
- Fourth one
Category Entertainment category has 2 shows
- First one
- Second one
Category Children category has 2 shows
- Fifth one
- Sixth one
Upvotes: 3
Reputation: 5148
try following code:
var response = [
["title": "First one", "code": "the_first", "category": "Entertainment category"],
["title": "Second one", "code": "the_second", "category": "Entertainment category"],
["title": "Third one", "code": "the_third", "category": "News category"],
["title": "Fourth one", "code": "the_fourth", "category": "News category"],
["title": "Fifth one", "code": "the_fifth", "category": "Children category"],
["title": "Sixth one", "code": "the_sixth", "category": "Children category"]
]
// Step1 convert array to dictionary
var categories = [String:[[String:String]]]()
for item in response {
if let category = item["category"] {
var channels = [[String:String]]()
if let resultCategory = categories[category] {
channels = resultCategory
}
channels.append(item)
categories[category] = channels
}
}
// Step2 get result
var result = [[String:Any]]()
for item in categories.keys {
let value = categories[item]!
result.append(["title": item, "channels":[value]])
}
result:
[["title": "News category", "channels": [[["title": "Third one", "code": "the_third", "category": "News category"], ["title": "Fourth one", "code": "the_fourth", "category": "News category"]]]], ["title": "Entertainment category", "channels": [[["title": "First one", "code": "the_first", "category": "Entertainment category"], ["title": "Second one", "code": "the_second", "category": "Entertainment category"]]]], ["title": "Children category", "channels": [[["title": "Fifth one", "code": "the_fifth", "category": "Children category"], ["title": "Sixth one", "code": "the_sixth", "category": "Children category"]]]]]
Upvotes: 0
Reputation: 7687
You have a couple of options for how to do this: the reduce
option does reduce (pun intended) mutable state in your code, but it's a much bigger performance hit, and less readable IMO. I would go for the forEach
if it was me.
forEach
:var response = [
["title": "First one", "code": "the_first", "category": "Entertainment category"],
["title": "Second one", "code": "the_second", "category": "Entertainment category"],
["title": "Third one", "code": "the_third", "category": "News category"],
["title": "Fourth one", "code": "the_fourth", "category": "News category"],
["title": "Fifth one", "code": "the_fifth", "category": "Children category"],
["title": "Sixth one", "code": "the_sixth", "category": "Children category"]
]
typealias ChannelsByCategory = [String: [[String: String]]]
var categories = ChannelsByCategory()
response.forEach { channel in
guard let channelCategory = channel["category"] else {
return
}
if var category = categories[channelCategory] {
category.append(channel)
} else {
categories[channelCategory] = [channel]
}
}
reduce
:let categories = response.reduce(ChannelsByCategory()) { dict, channel in
guard let channelCategory = channel["category"] else {
return dict
}
let channels = [channel] + (dict[channelCategory] ?? [])
var modifiedDict = dict
modifiedDict[channelCategory] = channels
return modifiedDict
}
typealias ResultDict = [[String: Any]]
let result: ResultDict = categories.map { name, channels in
return ["title": name, "channels": channels]
}
Upvotes: 0