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