CodeLair website tutorials, articles & scripts

Convert an array of numbers to a set of ranges

Published: 30 March 2010   •   Tags: php

Occasionally I have need to display a list of numbers, for example listing which options a user chose, or maybe a set of dates within a month. With many options chosen a plain comma-separated list can get unwieldy - however we can make it easier to read by combining sequential numbers into a set of ranges. Here’s some PHP code to do just that.

What we will do is take a list of numbers like this:

1, 2, 3, 4, 7, 8, 14, 18, 19, 20, 21, 22

And convert it to:

1-4, 7-8, 14, 18-22

We start with an array. Typically that would come from a select field or checkboxes in a form submission, or a database table where an object is mapped to many of the numbered items. I’ll use the same numbers as above in this example:

$numbers = [1, 2, 3, 4, 7, 8, 14, 18, 19, 20, 21, 22];

To create our range, we want to loop through each number and compare it to the next one. If it’s exactly 1 higher than the current number, it will be part of the range, otherwise it’s the start of a new range.

Our basic function will look like the following. We take an array of numbers as input, and output an array of strings containing the ranges.

function getRanges($nums)
{
$ranges = [];
$len = count($nums);

for ($i = 0; $i < $len; $i++) {
//...
}

return $ranges;
}

Inside the for loop, the current number is in $nums[$i] (so on the first iteration, $i will be 0 and $nums[$i] will be 1). We keep track of the start and end of the current range, and start by setting them to the same value:

$rangeStart = $rangeEnd = $nums[$i];

The next part is a bit more complicated. We create a nested while loop inside the for loop, which also increments the value of $i. In each iteration we check that the difference between the current and next numbers is 1. If so, we increment $i and set $rangeEnd value to that number; if not, we end the loop. We also need to use isset because on the last iteration, $nums[$i+1] will not exist - $i+1 will be out of bounds for the array.

while (isset($nums[$i+1]) && $nums[$i+1] - $nums[$i] == 1) {
$i++;
$rangeEnd = $nums[$i];
}

When the while loop exits, $rangeEnd is now set to the maximum value in this range. Now we create our range string - if the start/end are the same then it’s just one number, otherwise it’s the start and end, separated by a dash. We enclose both in qouble quotes to ensure consistency.

if ($rangeStart == $rangeEnd)
$ranges[] = "$rangeStart";
else
$ranges[] = "$rangeStart-$rangeEnd";

We can shorten this into one line with a ternary operator:

$ranges[] = $rangeStart == $rangeEnd ? "$rangeStart" : "$rangeStart-$rangeEnd";

Here’s the full function:

function getRanges($nums)
{
$ranges = [];
$len = count($nums);

for ($i = 0; $i < $len; $i++) {
$rangeStart = $rangeEnd = $nums[$i];
while (isset($nums[$i+1]) && $nums[$i+1] - $nums[$i] == 1) {
$i++;
$rangeEnd = $nums[$i];
}

$ranges[] = $rangeStart == $rangeEnd ? "$rangeStart" : "$rangeStart-$rangeEnd";
}

return $ranges;
}

And here’s how you use it:

$numbers = [1, 2, 3, 4, 7, 8, 14, 18, 19, 20, 21, 22];
$ranges = getRanges($numbers);

echo implode(', ', $ranges);
// prints "1-4, 7-8, 14, 18-22"

I hope this was useful!