What's the most efficient way to split an array of objects into multiple arrays based on a property?
For example, say we have the following items:
var items = [
Food(name: "Cupcake", foodType: .dessert),
Food(name: "Banana", foodType: .fruit),
Food(name: "Grandma's Soup", foodType: .custom("home-made"),
Food(name: "Ice-cream", foodType: .dessert),
]
What's the best way to split it into the following arrays based on foodType
:
[Food(name: "Cupcake", foodType: .dessert), Food(name: "Ice-cream", foodType: .dessert)]
[Food(name: "Banana", foodType: .fruit)],
[Food(name: "Grandma's Soup", foodType: .custom("home-made")]
In my example, foodType is an enum that looks like this:
enum FoodType {
case dessert
case fruit
case custom(string)
case vegetable
}
Normally I'd make my enum conform to CaseIterable
and then loop over allCases
, however, given that I'm using associated values, that's not possible.
My Approach:
var sections: [FoodType: [String]] = [:]
items.forEach {
sections[$0.foodType] = (sections[$0.foodType] ?? []) [$0.name]
}
CodePudding user response:
If the order of the groups doesn't matter, I would make FoodType
conform to Hashable
:
enum FoodType: Hashable {
case dessert
case fruit
case custom(String)
case vegetable
}
Then use the Dictionary.init(grouping:by:)
initialiser to create a [FoodType:[Food]]
:
Dictionary(grouping: items, by: \.foodType)
The values
of this dictionary are the groups you want:
let groups = [_](Dictionary(grouping: items, by: \.foodType).values)
This preserves the order of the elements in each group, but the order of the groups themselves is not preserved.
If you want to preserve the order of the groups, you can do:
let groups = items.reduce(into: [[Food]]()) { acc, food in
for i in acc.indices {
if acc[i][0].foodType == food.foodType {
acc[i].append(food)
return
}
}
acc.append([food])
}