0

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]
}
2
  • If there is .custom("foo") and .custom("bar"), do you want them to be in the same group or different groups? Commented Sep 10, 2022 at 1:59
  • Good question! Different groups. Commented Sep 10, 2022 at 2:01

1 Answer 1

1

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])
}
Sign up to request clarification or add additional context in comments.

8 Comments

If order does matter, reduce(into:) solves it; might want to show that too.
Oh wow this is so much cleaner than what I was thinking (I added my approach above). Also thank you for adding a way to have it ordered if needed. I imagine it's the same in speed, however I'll take the cleaner approach.
@matt You mean handwriting something similar to Dictionary.init(grouping:by:)?
Yes, just dealing into piles.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.