1

I want to do a str_replace() but only at the Nth occurrence.

Inputs:

$originalString = "Hello world, what do you think of today's weather"; 
$findString = ' ';
$nthOccurrence = 8;
$newWord = ' beautiful ';

Desired Output:

Hello world, what do you think of today's beautiful weather
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
Mr. B
  • 2,677
  • 6
  • 32
  • 42
  • Related: [How can I split a string in PHP at the nth occurrence of a needle?](https://stackoverflow.com/q/5956066/2943403) and [Truncate string after certain number of occurrences of a substring](https://stackoverflow.com/q/14428801/2943403) – mickmackusa May 07 '23 at 08:44

5 Answers5

2

Here is a tight little regex with \K that allows you to replace the nth occurrence of a string without repeating the needle in the pattern. If your search string is dynamic and might contain characters with special meaning, then preg_quote() is essential to the integrity of the pattern.

If you wanted to statically write the search string and nth occurrence into your pattern, it could be:

  • (?:.*?\K ){8}
  • or more efficiently for this particular case: (?:[^ ]*\K ){8}

\K tells the regex pattern to "forget" any previously matched characters in the fullstring match. In other words, "restart the fullstring match" or "Keep from here". In this case, the pattern only keeps the 8th space character.

Code: (Demo)

function replaceNth(string $input, string $find, string $replacement, int $nth = 1): string {
    $pattern = '/(?:.*?\K' . preg_quote($find, '/') . '){' . $nth . '}/';
    return preg_replace($pattern, $replacement, $input, 1);
}

echo replaceNth($originalString, $findString, $newWord, $nthOccurrence);
// Hello world, what do you think of today's beautiful weather

Another perspective on how to grapple the asked question is: "How to insert a new string after the nth instance of a search string?" Here is a non-regex approach that limits the explosions, prepends the new string to the last element then re-joins the elements. (Demo)

$originalString = "Hello world, what do you think of today's weather"; 
$findString = ' ';
$nthOccurrence = 8;
$newWord = 'beautiful ';   // notice that leading space was removed

function insertAfterNth($input, $find, $newString, $nth = 1) {
    $parts = explode($find, $input, $nth + 1);
    $parts[$nth] = $newString . $parts[$nth];
    return implode($find, $parts);
}

echo insertAfterNth($originalString, $findString, $newWord, $nthOccurrence);
// Hello world, what do you think of today's beautiful weather
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
0

I found an answer here - https://gist.github.com/VijayaSankarN/0d180a09130424f3af97b17d276b72bd

$subject = "Hello world, what do you think of today's weather"; 
$search = ' ';
$occurrence = 8;
$replace = ' nasty ';

/**
 * String replace nth occurrence
 * 
 * @param type $search      Search string
 * @param type $replace     Replace string
 * @param type $subject     Source string
 * @param type $occurrence  Nth occurrence
 * @return type         Replaced string
 */
function str_replace_n($search, $replace, $subject, $occurrence)
{

    $search = preg_quote($search);
    echo preg_replace("/^((?:(?:.*?$search){".--$occurrence."}.*?))$search/", "$1$replace", $subject);
}

str_replace_n($search, $replace, $subject, $occurrence);
Mr. B
  • 2,677
  • 6
  • 32
  • 42
  • `preg_quote()` has a `null` default delimiter. You should declare `/` as your pattern delimiter. https://www.php.net/manual/en/function.preg-quote.php – mickmackusa Apr 23 '21 at 03:37
0
$originalString = "Hello world, what do you think of today's weather"; 
$findString = ' ';
$nthOccurrence = 8;
$newWord = ' beautiful ';

$array = str_split($originalString);
$count = 0;
$num = 0;
foreach ($array as $char) {
    if($findString == $char){
        $count++;
    }
    $num++;
    if($count == $nthOccurrence){
        array_splice( $array, $num, 0, $newWord );
        break;
    }
}
$newString = '';
foreach ($array as $char) {
    $newString .= $char;
}

echo $newString;
  • This solution has performance issues. If the string is very large, `str_split` on it will form very large array which isn't optimal. – Clarity Feb 14 '23 at 14:35
0

I would consider something like:

function replaceNth($string, $substring, $replacement, $nth = 1){
  $a = explode($substring, $string); $n = $nth-1;
  for($i=0,$l=count($a)-1; $i<$l; $i++){
    $a[$i] .= $i === $n ? $replacement : $substring;
  }
  return join('', $a);
}
$originalString = 'Hello world, what do you think of today\'s weather';
$test = replaceNth($originalString, ' ', ' beautiful ' , 8);
$test2 = replaceNth($originalString, 'today\'s', 'good');
StackSlave
  • 10,613
  • 2
  • 18
  • 35
0

First explode a string by parts, then concatenate the parts together and with search string, but at specific number concatenate with replace string (numbers here start from 0 for convenience):

function str_replace_nth($search, $replace, $subject, $number = 0) {
    $parts = explode($search, $subject);
    $lastPartKey = array_key_last($parts);
    $result = '';
    foreach($parts as $key => $part) {
        $result .= $part;
        if($key != $lastPartKey) {
            if($key == $number) {
                $result .= $replace;
            } else {
                $result .= $search;
            }
        }
    }
    return $result;
}

Usage:

$originalString = "Hello world, what do you think of today's weather"; 
$findString = ' ';
$nthOccurrence = 7;
$newWord = ' beautiful ';
$result = str_replace_nth($findString, $newWord, $originalString, $nthOccurrence);
Clarity
  • 194
  • 3
  • 15
  • 1
    If the `count()` of `$parts` never changes in ypur script, why do you keep calling `count()` and performing subtraction on every iteration? Do you actually want `array_key_last()`? Did you consider limiting the explosions? – mickmackusa Feb 15 '23 at 09:03
  • @mickmackusa, yes, I actually want `array_key_last()`. Thank you for the improvement! – Clarity Feb 15 '23 at 11:10
  • 1
    ...but `array_key_last()`'s value never changes -- so it should be cached before looping. – mickmackusa Feb 15 '23 at 11:47
  • I see. So `array_key_last()` function is being called a lot of time which negatively affects performance. I've improved it. Hope that the new variant is better. – Clarity Feb 15 '23 at 11:56