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!