Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting array elements into range in php

Tags:

arrays

php

range

I’m working on an array of numeric values.

I have a array of numeric values as the following in PHP

11,12,15,16,17,18,22,23,24

And I’m trying to convert it into range for e.g in above case it would be:

11-12,15-18,22-24

I don’t have any idea how to convert it into range.

like image 540
Vishal Shetty Avatar asked Jun 08 '14 07:06

Vishal Shetty


4 Answers

You have to code it yourself ;-)

The algorithm is quite simple:

  • Iterate over the items.
  • Remember the previous item and the start of the range.
  • For each item (except the first one) check:
    • If currentItem = prevItem + 1 then you haven't found a new range. Continue.
    • Otherwise your range has ended. Write down the range. You have remembered the start of the range. The end is the previous item. The new range starts with the current item.
    • The first item always starts a new range. Remember this one as start of the range.
  • Don't forget to write down the current range when leaving the loop.
like image 120
theHacker Avatar answered Oct 29 '22 16:10

theHacker


I have used this one before, it does the trick.

Takes as input a comma separated string of numbers. Call to sort could be ignored if numbers are guaranteed to be sorted already.

function range_string($csv)
{
    // split string using the , character
    $number_array = array_map('intval', explode(',', $csv));
    sort($number_array);

    // Loop through array and build range string
    $previous_number = intval(array_shift($number_array)); 
    $range = false;
    $range_string = "" . $previous_number; 
    foreach ($number_array as $number) {
      $number = intval($number);
      if ($number == $previous_number + 1) {
        $range = true;
      }
      else {
        if ($range) {
          $range_string .= "-$previous_number";
          $range = false;
        }
        $range_string .= ",$number";
      }
      $previous_number = $number;
    }
    if ($range) {
      $range_string .= "-$previous_number";
    }

    return $range_string;
}

$csv_string = "11,16,12,17,18,15,22,23,24";
print range_string($csv_string); // 11-12,15-18,22-24
like image 40
davur Avatar answered Oct 29 '22 15:10

davur


Just adding my copy that is slightly different and supports a few extra things. I came here to compare it against other implementations. Here is test code to check the capability/correctness of my code:

$tests = [
    '1, 3, 5, 7, 9, 11, 13-15' => [1, 3, 5, 7, 9, 11, 13, 14, 15],
    '1-5'                      => [1, 2, 3, 4, 5],
    '7-10'                     => [7, 8, 9, 10],
    '1-3'                      => [1, 2, 3],
    '1-5, 10-12'               => [1, 2, 3, 4, 5, 10, 11, 12],
    '1-5, 7'                   => [1, 2, 3, 4, 5, 7],
    '10, 12-15'                => [10, 12, 13, 14, 15],
    '10, 12-15, 101'           => [10, 12, 13, 14, 15, 101],
    '1-5, 7, 10-12'            => [1, 2, 3, 4, 5, 7, 10, 11, 12],
    '1-5, 7, 10-12, 101'       => [1, 2, 3, 4, 5, 7, 10, 11, 12, 101],
    '1-5, 7, 10, 12, 14'       => [1, 2, 3, 4, 5, 7, 10, 12, 14],
    '1-4, 7, 10-12, 101'       => '1,2,3,4,7,10,11,12,101',
    '1-3, 5.5, 7, 10-12, 101'  => '1,2,3,5.5,7,10,11,12,101',
];

foreach($tests as $expectedResult => $array) {
    $funcResult = Utility::rangeToStr($array);
    if($funcResult != $expectedResult) {
        echo "Failed: result '$funcResult' != test check '$expectedResult'\n";
    } else {
        echo "Passed!: '$funcResult' == '$expectedResult'\n";
    }
}

The meat and potatoes, this is meant to be called statically within a class howver simply remove "static public" to use as a normal procedural function:

/**
 * Converts either a array of integers or string of comma-separated integers to a natural english range, such as "1,2,3,5" to "1-3, 5".  It also supports
 * floating point numbers, however with some perhaps unexpected / undefined behaviour if used within a range.
 *
 * @param string|array $items    Either an array (in any order, see $sort) or a comma-separated list of individual numbers.
 * @param string       $itemSep  The string that separates sequential range groups.  Defaults to ', '.
 * @param string       $rangeSep The string that separates ranges.  Defaults to '-'.  A plausible example otherwise would be ' to '.
 * @param bool|true    $sort     Sort the array prior to iterating?  You'll likely always want to sort, but if not, you can set this to false.
 *
 * @return string
 */
static public function rangeToStr($items, $itemSep = ', ', $rangeSep = '-', $sort = true) {
    if(!is_array($items)) {
        $items = explode(',', $items);
    }
    if($sort) {
        sort($items);
    }
    $point = null;
    $range = false;
    $str = '';
    foreach($items as $i) {
        if($point === null) {
            $str .= $i;
        } elseif(($point + 1) == $i) {
            $range = true;
        } else {
            if($range) {
                $str .= $rangeSep . $point;
                $range = false;
            }
            $str .= $itemSep . $i;
        }
        $point = $i;
    }
    if($range) {
        $str .= $rangeSep . $point;
    }

    return $str;
}
like image 43
A.B. Carroll Avatar answered Oct 29 '22 17:10

A.B. Carroll


If we have previous item and current item is not next number in sequence, then we put previous range (start-prev) in output array and current item will be start of next range, if we don't have previous item, then this item is the first item and as mentioned before - first item starts a new range. newItem function returns range or sigle number if there is no range. If you have unsorted array with repeating numbers, use sort() and array_unique() functions.

$arr = array(1,2,3,4,5,7,9,10,11,12,15,16);

function newItem($start, $prev)
{
    if ($start == $prev)
    {
        $result = $start;
    }
    else
    {
        $result = $start . '-' . $prev;
    }

    return $result;
}

foreach($arr as $item)
{
    if ($prev)
    {
        if ($item != $prev + 1)
        {
            $newarr[] = newItem($start, $prev);
            $start = $item;
        }
    }
    else
    {
        $start = $item;
    }
    $prev = $item;
}

$newarr[] = newItem($start, $prev);

echo implode(',', $newarr);

1-5,7,9-12,15-16

like image 30
serg2k Avatar answered Oct 29 '22 16:10

serg2k