How can I split a string in PHP at the nth occurrence of a needle?

后端 未结 12 1509
無奈伤痛
無奈伤痛 2020-11-29 08:25

There must be a fast and efficient way to split a (text) string at the "nth" occurrence of a needle, but I cannot find it. There is a fairly full set of functions

相关标签:
12条回答
  • 2020-11-29 08:41

    I've edited Galled's function to make it explode after every nth occurrences instead of just the first one.

    function split2($string, $needle, $nth) {
      $max = strlen($string);
      $n = 0;
      $arr = array();
    
      //Loop trough each character
      for ($i = 0; $i < $max; $i++) {
    
        //if character == needle
        if ($string[$i] == $needle) {
          $n++;
          //Make a string for every n-th needle
          if ($n == $nth) {
            $arr[] = substr($string, $i-$nth, $i);
            $n=0; //reset n for next $nth
          }
          //Include last part of the string
          if(($i+$nth) >= $max) {
            $arr[] = substr($string, $i + 1, $max);
            break;
          }
        }
      }
      return $arr;
    }
    
    0 讨论(0)
  • 2020-11-29 08:43

    Personally I'd just split it into an array with explode, and then implode the first n-1 parts as the first half, and implode the remaining number as the second half.

    0 讨论(0)
  • 2020-11-29 08:46

    Here's an approach that I would prefer over a regexp solution (see my other answer):

    function split_nth($str, $delim, $n)
    {
      return array_map(function($p) use ($delim) {
          return implode($delim, $p);
      }, array_chunk(explode($delim, $str), $n));
    }
    

    Just call it by:

    split_nth("1 2 3 4 5 6", " ", 2);
    

    Output:

    array(3) {
      [0]=>
      string(3) "1 2"
      [1]=>
      string(3) "3 4"
      [2]=>
      string(3) "5 6"
    }
    
    0 讨论(0)
  • 2020-11-29 08:48

    If your needle will always be one character, use Galled's answer. It's going to be faster by quite a bit. If your $needle is a string, try this. It seems to work fine.

    function splitn($string, $needle, $offset)
    {
        $newString = $string;
        $totalPos = 0;
        $length = strlen($needle);
        for($i = 0; $i < $offset; $i++)
        {
            $pos = strpos($newString, $needle);
    
            // If you run out of string before you find all your needles
            if($pos === false)
                return false;
            $newString = substr($newString, $pos + $length);
            $totalPos += $pos + $length;
        }
        return array(substr($string, 0, $totalPos-$length), substr($string, $totalPos));
    }
    
    0 讨论(0)
  • 2020-11-29 08:48

    I really like Hamze GhaemPanah's answer for its brevity. However, there's a small bug in it.

    In the original code:

    $i = $pos = 0;
    do {
        $pos = strpos($string, $needle, $pos+1);
    } while( $i++ < $nth);
    

    $nth in the do while loop should be replaced with ($nth-1) as it will incorrectly iterate one extra time - setting the $pos to the position of the $nth+1 instance of the needle. Here's an example playground to demonstrate. If this link fails here is the code:

    $nth = 2;
    $string = "44 E conway ave west horse";
    $needle = " ";
    
    echo"======= ORIGINAL =======\n";
    
    $i = $pos = 0;
    do {
        $pos = strpos($string, $needle, $pos + 1);
    } while( $i++ < $nth);
    
    echo "position: $pos \n";
    echo substr($string, 0, $pos) . "\n\n";
    
    /*
        Outputs:
    
        ======= ORIGINAL =======
        position: 11
        44 E conway
    */
    
    echo"======= FIXED =======\n";
    
    $i = $pos = 0;
    do {
        $pos = strpos($string, $needle, $pos + 1);
    } while( $i++ < ($nth-1) );
    
    echo "position: $pos \n";
    echo substr($string, 0, $pos);
    
    /*
        Outputs:
    
        ======= FIXED =======
        position: 4
        44 E
    
    */
    

    That is, when searching for the position of the second instance of our needle, our loop iterates one extra time setting $pos to the position of our third instance of our needle. So, when we split the string on the second instance of our needle - as the OP asked - we get the incorrect substring.

    0 讨论(0)
  • 2020-11-29 08:50

    This is ugly, but it seems to work:

    $foo = '1 2 3 4 5 6 7 8 9 10 11 12 13 14';
    
    $parts = preg_split('!([^ ]* [^ ]* [^ ]*) !', $foo, -1,
                PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
    
    var_dump($parts);
    

    Output:

    array(5) {
      [0]=>
      string(5) "1 2 3"
      [1]=>
      string(5) "4 5 6"
      [2]=>
      string(5) "7 8 9"
      [3]=>
      string(8) "10 11 12"
      [4]=>
      string(5) "13 14"
    }
    

    Replace the single spaces in the query with a single character you wish to split on. This expression won't work as-is with multiple characters as the delimiter.

    This is hard coded for every third space. With a little tweaking, probably could be easily adjusted. Although a str_repeat to build a dynamic expression would work as well.

    0 讨论(0)
提交回复
热议问题