Real world example of the power of collections

Jan 29, 2022

You've probably seen a lot of tweets and blog posts about how collections can make your code more readable and compact, reducing if statements and ugly for loops.

But when you try to implement them in your own code, it's more often than not a little more complicated than filter this and map that.

That's why today I wanted to share with you this post I came across while browsing the laravel subreddit, it reads like this:

Sorting a collection in different orders based on conditional for column value

Basically I have a collection of products, which all have location codes.

These codes start with W1 or W2 or W3, followed by another letter and some numbers (irrelevant beyond the third character for this problem)

So for example: W1A..., W1B..., W2P..., W2Z... Now the thing is I want to sort the products in a specific order depending on the location codes.

The order should be W2 first, but in the order of W2Z... to W2A..., and after that comes W1, in order of W1A... to W1Z..., and after that W3 in W3A... to W3Z...

How could I write a custom sort function / query that achieves this order of the products, based on the string value of their location?

Understanding the problem

When I first read this post, I didn't really understand a whole lot of the problem, so I'll try to explain it a little bit more clearly.

We have a collection of products, each product has a "location code" (ie. a place in a warehouse). And the original poster needs to sort these products following the optimal walking sequence through a warehouse, which is the following:

  1. First all the codes that start with W2 should be ordered in reverse alphabetical order (W2Z, W2Y, ...., W2A)
  2. Then after those codes the codes that start with W1 should follow in alphabetical order (W1A, W1B..., W1Z)
  3. Finally the codes that start with W3 should be in alphabetical order as well (W3A, W3B..., W3Z)

For example a list of ordered codes could look like this:

$codes = ['W2Z', 'W2A', 'W1A', 'W1Z', 'W3A', 'W3Z']

Finding a solution

This is a very weird way to sort a list of products, the second character, the number, doesn't go in ascending or descending order, and the third character also has no order.

To me, this looks like 3 separate lists that need to be sorted independently. Maybe only two since the W1 and W3 are infarct consecutive in ascending order, but I'll ignore that fact in this exercise and treat them as if they were not.

The loop solution

Since we are treating them as a separated list, it makes sense to separate them into different variables.

$codes = ['W2Z', 'W2A', 'W1A', 'W1Z', 'W3A', 'W3Z', 'W2C', 'W3B', 'W1Y'];

$w2s = [];
$w1s = [];
$w3s = [];

foreach ($codes as $code){
    if (str_starts_with($code, 'W2'))  {
        $w2s[] = $code;
    } elseif (str_starts_with($code, 'W1')) {
        $w1s[] = $code;
    } else {
        $w3s[] = $code;
    }
}

Now, all we have to do is sort each of our new arrays with their own sorting order and then merge them in the correct order.

// the W2 array is in reverse sort order so it goes from Z to A
rsort($w2s);
sort($w1s);
sort($w3s);

$codes = [...$w2s, ...$w1s, ...$w3s];

This code works... but is it readable. You could argue that (I've argued for worse code during code reviews) but you still get the feeling that it could be better.

Refactoring to collections

With collections we have access to a lot of useful methods that we can use to make this code a little better, I'm going to follow the same formula as above (separated them into different lists and then merge them). Here is what I came up with:

$codes = collect(['W2Z', 'W2A', 'W1A', 'W1Z', 'W3A', 'W3Z', 'W2C', 'W3B', 'W1Y']);

$codes = [
    ...$codes->filter(fn($item) => str_starts_with($item, 'W2'))->sortDesc(),
    ...$codes->filter(fn($item) => str_starts_with($item, 'W1'))->sort(),
    ...$codes->filter(fn($item) => str_starts_with($item, 'W3'))->sort()
];

As you can see this is waaaaay shorter than the previous version and it might seem confusing at first, but once you get used to working with collections this is also way easier to read.