Converting array elements into range in php

后端 未结 7 1793
时光说笑
时光说笑 2021-01-02 11:39

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

相关标签:
7条回答
  • 2021-01-02 12:19

    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
    
    0 讨论(0)
  • 2021-01-02 12:23

    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

    0 讨论(0)
  • 2021-01-02 12:24

    You could do it like that:

    $numbers = [11,12,15,16,17,18,22,23,24];
    $ranges  = [];
    $start   = $end = current($numbers);
    
    foreach($numbers as $range){
        if($range - $end > 1){
            $ranges[] = ($start == $end) ? $start : $start . "-" . $end;
            $start    = $range;
        }
        $end = $range;
    }
    $ranges[] = ($start == $end) ? $start : $start . "-" . $end;
    
    0 讨论(0)
  • 2021-01-02 12:24

    The answer provided by Ali Gajani kept giving me "Illegal offset" warnings. So, because I figured somebody might want to use it as badly as I do, I'm posting my fixes here - though note that my fixes might be deemed silly by an advanced programmer - it does seem to work now with no issues.

    I replaced/tweaked two parts of the code. Below you will see what I added (marked in bold) and what was removed, which I commented (//).

    As near as I can tell, it was having trouble on the first pass because there was no "previous pass" to refer to (hence, it was balking at "$previous = $array[$i-1];") - and on the last pass for a similar reason. In that second instance, I simply moved "$next_key = $break_start[$i+1];" below the last iteration check.

    $break_start = array();
    
    //range finder
    for ($i=0; $i<sizeof($array); $i++) {
        $current = $array[$i]; 
    **if($i>0) {
            $previous = $array[$i-1];
    }
    else {
            $previous = $current;
    }**
      //  $previous = $array[$i-1];
        if ($current==($previous+1)) { 
            //no break points are found 
        } else { 
            //return break points with keys intact
            array_push($break_start, $i);
        }
    
    }
    
    for ($i=0; $i<sizeof($break_start); $i++) {
        $key = $break_start[$i];
    //    $next_key = $break_start[$i+1];
    
        //if last iteration
        if ($i==sizeof($break_start)-1) { 
            echo "Range: ".$array[$key]." - ".$array[count($array)-1]." \n"; 
            } 
        else { 
        **$next_key = $break_start[$i+1];**
            echo "Range: ".$array[$key]." - ".$array[$next_key-1]." \n";    
            }
    }
    

    If there are smarter ways to get this done, please advise. But I searched high and low for this - and figured someone else might also benefit. Another tip of the hat to Ali Gajani for his initial help.

    0 讨论(0)
  • 2021-01-02 12:25

    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.
    0 讨论(0)
  • 2021-01-02 12:30

    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;
    }
    
    0 讨论(0)
提交回复
热议问题