Can the for loop be eliminated from this piece of PHP code?

后端 未结 10 1092
夕颜
夕颜 2020-12-11 04:59

I have a range of whole numbers that might or might not have some numbers missing. Is it possible to find the smallest missing number without using a loop structure? If ther

相关标签:
10条回答
  • 2020-12-11 05:30
    function missing( $v ) {
        static $p = -1;
        $d = $v - $p - 1;
        $p = $v;
        return $d?1:0;
    }
    
    $result = array_search( 1, array_map( "missing", $ARRAY_TO_TEST ) );
    
    0 讨论(0)
  • 2020-12-11 05:32

    Algo solution

    There is a way to check if there is a missing number using an algorithm. It's explained here. Basically if we need to add numbers from 1 to 100. We don't need to calculate by summing them we just need to do the following: (100 * (100 + 1)) / 2. So how is this going to solve our issue ?

    We're going to get the first element of the array and the last one. We calculate the sum with this algo. We then use array_sum() to calculate the actual sum. If the results are the same, then there is no missing number. We could then "backtrack" the missing number by substracting the actual sum from the calculated one. This of course only works if there is only one number missing and will fail if there are several missing. So let's put this in code:

      $range = range(0,7);  // Creating an array
      echo check($range) . "\r\n"; // check
      unset($range[3]); // unset offset 3
      echo check($range); // check
        
      function check($array){
        if($array[0] == 0){
          unset($array[0]); // get ride of the zero
        }
        sort($array); // sorting
        $first = reset($array); // get the first value
        $last = end($array); // get the last value
        $sum = ($last * ($first + $last)) / 2; // the algo
        $actual_sum = array_sum($array); // the actual sum
        if($sum == $actual_sum){
          return $last + 1; // no missing number
        }else{
          return $sum - $actual_sum; // missing number
        }
      }
    

    Output

    8
    3
    

    Online demo

    If there are several numbers missing, then just use array_map() or something similar to do an internal loop.


    Regex solution

    Let's take this to a new level and use regex ! I know it's nonsense, and it shouldn't be used in real world application. The goal is to show the true power of regex :)

    So first let's make a string out of our range in the following format: I,II,III,IIII for range 1,3.

    $range = range(0,7);
    if($range[0] === 0){ // get ride of 0
      unset($range[0]);
    }
    
    $str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
    echo $str;
    

    The output should be something like: I,II,III,IIII,IIIII,IIIIII,IIIIIII.

    I've come up with the following regex: ^(?=(I+))(^\1|,\2I|\2I)+$. So what does this mean ?

    ^                   # match begin of string
    (?=                 # positive lookahead, we use this to not "eat" the match
        (I+)            # match I one or more times and put it in group 1
    )                   # end of lookahead
    (                   # start matching group 2
        ^\1             # match begin of string followed by what's matched in group 1
            |           # or
        ,\2I            # match a comma, with what's matched in group 2 (recursive !) and an I
            |           # or
        \2I             # match what's matched in group 2 and an I
    )+                  # repeat one or more times
    $                   # match end of line
    

    Let's see what's actually happening ....

    I,II,III,IIII,IIIII,IIIIII,IIIIIII
    ^
    (I+) do not eat but match I and put it in group 1
    
    I,II,III,IIII,IIIII,IIIIII,IIIIIII
    ^
    ^\1 match what was matched in group 1, which means I gets matched
    
    I,II,III,IIII,IIIII,IIIIII,IIIIIII
     ^^^ ,\2I match what was matched in group 1 (one I in thise case) and add an I to it
    
    I,II,III,IIII,IIIII,IIIIII,IIIIIII
        ^^^^ \2I match what was matched previously in group 2 (,II in this case) and add an I to it
    
    I,II,III,IIII,IIIII,IIIIII,IIIIIII
            ^^^^^ \2I match what was matched previously in group 2 (,III in this case) and add an I to it
    
    We're moving forward since there is a + sign which means match one or more times,
    this is actually a recursive regex.
    We put the $ to make sure it's the end of string
    If the number of I's don't correspond, then the regex will fail.
    

    See it working and failing. And Let's put it in PHP code:

    $range = range(0,7);
    if($range[0] === 0){
      unset($range[0]);
    }
    
    $str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
    if(preg_match('#^(?=(I*))(^\1|,\2I|\2I)+$#', $str)){
      echo 'works !';
    }else{
      echo 'fails !';
    }
    

    Now let's take in account to return the number that's missing, we will remove the $ end character to make our regex not fail, and we use group 2 to return the missed number:

    $range = range(0,7);
    if($range[0] === 0){
      unset($range[0]);
    }
    unset($range[2]); // remove 2
    
    $str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
    preg_match('#^(?=(I*))(^\1|,\2I|\2I)+#', $str, $m); // REGEEEEEX !!!
    
    $n = strlen($m[2]); //get the length ie the number
    $sum = array_sum($range); // array sum
    
    if($n == $sum){
      echo $n + 1; // no missing number
    }else{
      echo $n - 1; // missing number
    }
    

    Online demo

    0 讨论(0)
  • 2020-12-11 05:45
    $range = array(0,1,2,3,4,6,7);    
    // sort just in case the range is not in order
    asort($range);
    $range = array_values($range);
    $indexes = array_keys($range);
    $diff = array_diff($indexes,$range);
    
    echo $diff[0]; // >> will print: 5 
    // if $diff is an empty array - you can print 
    // the "maximum value of the range plus one": $range[count($range)-1]+1
    
    0 讨论(0)
  • 2020-12-11 05:45

    you can use array_diff() like this

    <?php
            $range = array("0","1","2","3","4","6","7","9");
            asort($range);
    
        $len=count($range);
        if($range[$len-1]==$len-1){
          $r=$range[$len-1];
       }
        else{
        $ref= range(0,$len-1);
        $result = array_diff($ref,$range);
        $r=implode($result);
    }
    echo $r;
    
    ?>
    
    0 讨论(0)
  • 2020-12-11 05:46

    Simple

    $array1 = array(0,1,2,3,4,5,6,7);// array with actual number series
    $array2 = array(0,1,2,4,6,7); // array with your custom number series
    $missing = array_diff($array1,$array2);
    sort($missing);
    echo $missing[0]; 
    
    0 讨论(0)
  • 2020-12-11 05:47
    $range = array(0,1,2,3,4,6,7);
    
    $max=max($range);
    
    $expected_total=($max*($max+1))/2; // sum if no number was missing.
    
    $actual_total=array_sum($range);  // sum of the input array.
    
    if($expected_total==$actual_total){
       echo $max+1;      // no difference so no missing number, then echo 1+ missing number.
    }else{
       echo $expected_total-$actual_total; // the difference will be the missing number.
    }
    
    0 讨论(0)
提交回复
热议问题