I want to take a list of years and collapse them down to hyphenated ranges. It works better for small screen on mobile devices, displaying “2009-2016” instead of “2009, 2010, 2011, 2012, etc.”

e.g., there are 3 transforms on the array of integers [2015, 2017, 2006, 2010, 2009, 2018, 2008]

  1. sort the years

    [2006, 2008, 2009, 2010, 2015, 2017, 2018]

  2. group into subsequent years

    [[2006], [2008, 2009, 2010], [2015], [2017, 2018]]

  3. collapse into ranges

    [‘2006’, ‘2008-2010’, ‘2015’, ‘2017-2018’]


For a 0-indexed array, an integer key groupBy() finding the neighboring difference of 1 solves this problem. It’s the magic of maths.

I’ve implemented this solution using a collection pipeline. Third-party PHP packages illuminate/support (tightenco/collect is a Collection-only subset) or dusank/knapsack will do the trick. They give an expressive, functional approach to transforming PHP arrays.

$years = [2015, 2017, 2006, 2010, 2009, 2018, 2008];

collect($years)
    ->sort()
    ->unique()
    ->values()
    ->groupBy(function ($year, $key) {
        return $key - $year;
    })
    ->map(function ($years) {
        return $years->bookends()->implode('-');
    })
    ->values()
    ->all();

// ['2006', '2008-2010', '2015', '2017-2018']

bookends() is an additional function added to Laravel’s macroable Collection class.

use Illuminate\Support\Collection;

Collection::macro('bookends', function () {
    $items = [];

    if ($this->isNotEmpty()) {
        $items[] = $this->first();
    }

    if ($this->count() > 1) {
        $items[] = $this->last();
    }

    return new static($items);
});

Without a for loop in sight, I’ve used this solution to show what years a football player laced up the boots in their career.