【问题标题】:How can I split a string in PHP at the nth occurrence of a needle?如何在第 n 次出现针时拆分 PHP 中的字符串?
【发布时间】:2011-05-10 20:29:36
【问题描述】:

必须有一种快速有效的方法来在针的“第 n 个”出现处拆分(文本)字符串,但我找不到它。 strpos comments in the PHP manual 中有相当完整的功能集,但这似乎有点满足我的需要。

我的纯文本为$string,并希望在$needle 的nth 次出现时将其拆分,在我的情况下,needle 只是一个空格。 (我可以进行完整性检查!)

我该怎么做?

【问题讨论】:

标签: php string


【解决方案1】:

可能是:

function split2($string, $needle, $nth) {
    $max = strlen($string);
    $n = 0;
    for ($i=0; $i<$max; $i++) {
        if ($string[$i] == $needle) {
            $n++;
            if ($n >= $nth) {
                break;
            }
        }
    }
    $arr[] = substr($string, 0, $i);
    $arr[] = substr($string, $i+1, $max);

    return $arr;
}

【讨论】:

  • 只适用于一个字符长度的$needle
【解决方案2】:

就我个人而言,我只是将它拆分为一个带有爆炸的数组,然后将第一个 n-1 部分作为前半部分内爆,并将剩余的数字作为后半部分内爆。

【讨论】:

    【解决方案3】:

    如果您的指针始终是一个字符,请使用Galled's answer。它会快很多。如果你的 $needle 是一个字符串,试试这个。它似乎工作正常。

    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));
    }
    

    【讨论】:

    • 正如你所注意到的,我的情况需要一个单字符的“针”,但最好在线程中有这个解决方案。谢谢!
    【解决方案4】:

    与正则表达式解决方案相比,这是我更喜欢的一种方法(请参阅我的其他答案):

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

    只需通过以下方式调用它:

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

    输出:

    array(3) {
      [0]=>
      string(3) "1 2"
      [1]=>
      string(3) "3 4"
      [2]=>
      string(3) "5 6"
    }
    

    【讨论】:

    • 当然,这解决了一个稍微不同的问题——不是“在 nth 字符处分割”,而是“在每个 nth 字符处分割。不完全我的方案!不过可能对其他人有用。谢谢!
    • 我已经编辑了答案来处理你的观点。但是无论编辑是否被接受,我都将其组合成一个完整的答案here。
    【解决方案5】:

    采用Matthew's answer 并为Dɑvïd's comment 添加解决方案:

    function split_nth($str, $delim, $n) {
      $result = array_map(function($p) use ($delim) {
          return implode($delim, $p);
      }, array_chunk(explode($delim, $str), $n));
      $result_before_split = array_shift($result);
      $result_after_split = implode(" ", $result); 
      return array($result_before_split, $result_after_split);
    }
    

    只需通过以下方式调用它:

    list($split_before, $split_after) = split_nth("1 2 3 4 5 6", " ", 2);
    

    输出:

    1 2
    3 4 5 6
    

    【讨论】:

      【解决方案6】:

      你可以使用类似下面的东西:

      /* Function copied from the PHP manual comment you referenced */
      function strnripos_generic( $haystack, $needle, $nth, $offset, $insensitive, $reverse )
      {
          //  If needle is not a string, it is converted to an integer and applied as the ordinal value of a character.
          if(! is_string($needle)) {
              $needle = chr((int)$needle);
          }
      
          //  Are the supplied values valid / reasonable?
          $len = strlen($needle);
          if(1 > $nth || 0 === $len) {
              return false;
          }
      
          if($insensitive) {
              $haystack = strtolower($haystack);
              $needle   = strtolower($needle  );
          }
      
          if($reverse) {
              $haystack = strrev($haystack);
              $needle   = strrev($needle  );
          }
      
          //  $offset is incremented in the call to strpos, so make sure that the first
          //  call starts at the right position by initially decreasing $offset by $len.
          $offset -= $len;
          do
          {
              $offset = strpos($haystack, $needle, $offset + $len);
          } while(--$nth && false !== $offset);
      
          return false === $offset || ! $reverse ? $offset : strlen($haystack) - $offset;
      }
      
      // Our split function
      function mysplit ($haystack, $needle, $nth) {
          $position = strnripos_generic($haystack, $needle, $nth, 0, false, false);
          $retval = array();
      
          if ($position !== false) {
              $retval[0] = substr($haystack, 0, $position-1);
              $retval[1] = substr($haystack, $position);
              return $retval;
          }
      
          return false;
      }
      

      然后你只需使用 mysplit 函数,你就会得到一个包含两个子字符串的数组。第一个包含直到第 n 次出现的 needle(不包括)的所有字符,第二个包含从第 n 次出现的 needle(包括)到末尾的所有字符。

      【讨论】:

      • 这无疑使使用这些函数进行拆分更易于管理。 Galled 的较短解决方案对我有用,但这对于比我更复杂的情况可能有用。谢谢!
      【解决方案7】:

      这很难看,但似乎有效:

      $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);
      

      输出:

      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"
      }
      

      用您希望分割的单个字符替换查询中的单个空格。此表达式无法按原样使用多个字符作为分隔符。

      每隔三个空格就硬编码一次。稍加调整,可能很容易调整。尽管使用str_repeat 来构建动态表达式也可以。

      【讨论】:

        【解决方案8】:

        我已经编辑了Galled's function,让它在每第 n 次出现而不是第一次出现后爆炸。

        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;
        }
        

        【讨论】:

          【解决方案9】:

          简单,做吧:

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

          【讨论】:

          • 感谢@Vardkin 找到我的错误!我修好了。
          • do ... while 提出问题是否存在它无法处理的情况。有吗?
          • Vardkin's answer 声称这不起作用。您可以edit your answer(没有“编辑:”、“更新:”或类似内容)。
          【解决方案10】:

          我真的很喜欢 Hamze GhaemPanah's answer 的简洁性。但是,它有一个小错误。

          在原代码中:

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

          do while 循环中的$nth 应替换为($nth-1),因为它会错误地重复一次额外的时间——将$pos 设置为针的$nth+1 实例的位置。这是一个例子playground to demonstrate。如果此链接失败,这里是代码:

          $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
          
          */
          

          也就是说,当搜索我们的第二个实例的位置时,我们的循环重复了一个额外的时间,将$pos 设置为我们的第三个实例的位置。因此,当我们在针的第二个实例上拆分字符串时 - 正如 OP 所要求的那样 - 我们得到了不正确的子字符串。

          【讨论】:

            【解决方案11】:
            function strposnth($haystack,$needle,$n){
              $offset = 0;
              for($i=1;$i<=$n;$i++){
                $indx = strpos($haystack, $needle, $offset);
                if($i == $n || $indx === false)
                    return $indx;
                else {
                    $offset = $indx+1;
                }
              }
              return false;
            }
            

            【讨论】:

              【解决方案12】:
              function split_nth($haystack, $needle, $nth){
                  $result = array();
                  if(substr_count($haystack,$needle) > ($nth-1)){
                      $haystack = explode($needle, $haystack);
                      $result[] = implode(array_splice($haystack, 0, $nth), $needle);
                      $result[] = implode($haystack, $needle);
                  }
                  return $result;
              }
              

              【讨论】:

              • 你能详细说明你的答案吗?发布一段没有任何文字的代码通常不是很有帮助。
              猜你喜欢
              • 2019-04-02
              • 2021-08-10
              • 1970-01-01
              • 1970-01-01
              • 2017-12-24
              • 1970-01-01
              • 2013-06-08
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多