188

Anyone know of a very fast way to replace the last occurrence of a string with another string in a string?

Note, the last occurrence of the string might not be the last characters in the string.

Example:

$search = 'The';
$replace = 'A';
$subject = 'The Quick Brown Fox Jumps Over The Lazy Dog';

Expected Output:

The Quick Brown Fox Jumps Over A Lazy Dog
2

15 Answers 15

277

You can use this function:

function str_lreplace($search, $replace, $subject)
{
    $pos = strrpos($subject, $search);

    if($pos !== false)
    {
        $subject = substr_replace($subject, $replace, $pos, strlen($search));
    }

    return $subject;
}
Sign up to request clarification or add additional context in comments.

8 Comments

This was still returning true no matter what. Consider modifying it to be: if($pos) { $subject = substr_replace($subject, $replace, $pos, strlen($search)); return $subject; } else { return false; }
@Jason It doesn't return TRUE no matter what. It returns a string no matter what. If a replacement can't be made it returns the original $subject, just like substr_replace and str_replace do.
@Mischa Isn't that the same thing in this case? I was attempting to do something like !str_lreplace, but if it doesn't return false, it's considered true, right? Either way, this helped me out and I appreciate it. Thanks.
How can this work? strpos — Find the position of the first occurrence of a substring in a string - edit: wow. Php geniuses really made a function called strpos and strrpos ? Thanks....
@Barry this is one case where PHP does not deserve the blame :-) The names are patterned on the decades-old strstr, strrstr of the standard C library, which are the same functions. (But did they have to change the name?)
|
35

Another 1-liner but without preg:

$subject = 'bourbon, scotch, beer';
$search = ',';
$replace = ', and';

echo strrev(implode(strrev($replace), explode(strrev($search), strrev($subject), 2))); //output: bourbon, scotch, and beer

2 Comments

FWIW, the accepted solution is roughly 35% faster than this solution.
I would always chose a regex function over 6 function calls. I can't imagine seeing this technique employed in professional code.
28
$string = 'this is my world, not my world';
$find = 'world';
$replace = 'farm';
$result = preg_replace(strrev("/$find/"),strrev($replace),strrev($string),1);
echo strrev($result); //output: this is my world, not my farm

2 Comments

Why it works with all strings reversed? Is there some (I assume) specific performance gain when using regular-expressions?
No it actually decreases performance but it's because you want the last occurrence only so you limit the search to one and reverse everything if you wanted the first you wouldn't have to reverse anything
21

The following rather compact solution uses the PCRE positive lookahead assertion to match the last occurrence of the substring of interest, that is, an occurrence of the substring which is not followed by any other occurrences of the same substring. Thus the example replaces the last 'fox' with 'dog'.

$string = 'The quick brown fox, fox, fox jumps over the lazy fox!!!';
echo preg_replace('/(fox(?!.*fox))/', 'dog', $string);

OUTPUT: 

The quick brown fox, fox, fox jumps over the lazy dog!!!

1 Comment

I don't understand why this later answer has received more upvotes than Alix Axel's answer. This pattern take 3 times as many "steps" compared to Alix's pattern. The negative lookahead is an unnecessary expense because it doesn't improve accuracy at all -- versus greedy dot matching.
13

You could do this:

$str = 'Hello world';
$str = rtrim($str, 'world') . 'John';

Result is 'Hello John';

4 Comments

This works as long as there isn't any repeated characters. In my situation I'm striping the page number off the archive date so I have "2015-12/2" and it takes all / and all 2 off the end becoming "2015-1".
This will only work if the last occurrence searched is the last word and has no additional chars after it.
This doesn't work because rtrim doesn't behave the way you're thinking. It will strip from the end any characters that exist in the search string in whatever order (and always append the replacement), e.g. "Hello word" -> "Hello John", "Hello lord" -> "Hello John", "Hello motor" -> "Hello motJohn", "Hello worldy" -> "Hello worldyJohn".
I don't recommend that anyone use this (unexplained) code which is prone to quietly failing. This answer makes no attempt to "find" the last occurrence of a string -- it just keeps consuming characters from the right side of the input string while the character mask is satisfied. This does the same thing: rtrim($str, 'dlorw')
10

This is an ancient question, but why is everyone overlooking the simplest regexp-based solution? Normal regexp quantifiers are greedy, people! If you want to find the last instance of a pattern, just stick .* in front of it. Here's how:

$text = "The quick brown fox, fox, fox, fox, jumps over etc.";
$fixed = preg_replace("((.*)fox)", "$1DUCK", $text);
print($fixed);

This will replace the last instance of "fox" to "DUCK", like it's supposed to, and print:

The quick brown fox, fox, fox, DUCK, jumps over etc.

5 Comments

Thanks! The perfect function to wrap around my expression to accomplish this. In my case I'm replacing the last comma with ", and ". Glad I scrolled down a bit in this answer list.
The "simplest regexp-based solution" was "overlooked" by you -- this technique was posted 8 years earlier on this page by Alix Axel. His/Her answer is identical except his/her snippet is dynamic. Your answer has hardcoded the needle into the search pattern and the replacement string into the replace parameter. I have gone one step further to simplify the replacement by using the \K character here
@mickmackusa, you're right, good catch. Indeed Alix Axel's answer uses greedy search on the left... but let's just say I don't see it as particularly "simple" :-D. (Also there is no reason to use ~ as the delimiter... just makes things more confusing.)
I would argue that your parenthetical delimiters are more confusing to me -- and I am very comfortable with reading regex. I often use tildes as delimiters, even if there are no forward slashes in the pattern -- it is my personal preference. Seeing parentheses in a pattern, my mind assumes a capture group or lookaround. Alix's answer IS less simple mostly because it is dynamic. My answer tightens up the syntax slightly.
the parens are a capture group.
7

This will also work:

function str_lreplace($search, $replace, $subject)
{
    return preg_replace('~(.*)' . preg_quote($search, '~') . '(.*?)~', '$1' . $replace . '$2', $subject, 1);
}

UPDATE Slightly more concise version (http://ideone.com/B8i4o):

function str_lreplace($search, $replace, $subject)
{
    return preg_replace('~(.*)' . preg_quote($search, '~') . '~', '$1' . $replace, $subject, 1);
}

3 Comments

Am i doing it wrong ? If so just ignore me :) ||| echo str_lreplace("x", "y", "this x or that x"); => Output: "y" See: ideone.com/UXuTo
@edorian: Oops! Sorry, I posted that in a hurry, the correct version is here: ideone.com/vR073.
This answer is missing its educational explanation.
4

You can use strrpos() to find last match.

$string = "picture_0007_value";
$findChar =strrpos($string,"_");

$string[$findChar]=".";

echo $string;

Output : picture_0007.value

1 Comment

This is only correct when replacing a single-byte character string which has a length of one AND the search character exists -- in all other cases, this fails.
3
$string = "picture_0007_value";
$findChar =strrpos($string,"_");
if($findChar !== FALSE) {
  $string[$findChar]=".";
}

echo $string;

Apart from the mistakes in the code, Faruk Unal has the best anwser. One function does the trick.

3 Comments

You need to check, if $findChar is not false (same way as in the accepted answer). If the string does not contain searched string, you get notice and the first character will be replaced.
This is great, but as it stands it can only replace 1 character with 1 character.
The above comments are correct. This is only correct when replacing a single-byte character string which has a length of one.
2

Shorthand for accepted answer

function str_lreplace($search, $replace, $subject){ 
    return is_numeric($pos=strrpos($subject,$search))?
    substr_replace($subject,$replace,$pos,strlen($search)):$subject;
}

1 Comment

This is also less readable than the accepted answer. Why not use a better coding standard? Please read the PSR coding standards.
0

A short version:

$NewString = substr_replace($String,$Replacement,strrpos($String,$Replace),strlen($Replace));

1 Comment

This unexplained "short version" (less readable version) should not be used because if the needle ($Replace) is not found, then the string ($String) is unintentionally damaged/mutated. This is a mistake that earlier answers did not make. This answer should be removed so that researchers do not copy this inappropriate snippet. 3v4l.org/easUY
0

While using regex is typically less performant than non-regex techniques, I do appreciate the control and flexibility that it affords.

In my snippet, I will set the pattern to be case-insensitive (\i, although my sample input will not challenge this rule) and include word boundaries (\b, although they were not explicitly called for).

I am also going to use the \K metacharacter to reset the fullstring match so that no capture groups / backreferences are needed.

Code: (Demo)

$search = 'The';
$replace = 'A';
$subject = "The Quick Brown Fox Jumps Over The Lazy Dog's Thermos!";

echo preg_replace(
         '/.*\K\b' . preg_quote($search, '/') . '\b/i',
         $replace,
         $subject
     );

Output:

  The Quick Brown Fox Jumps Over A Lazy Dog's Thermos!
# ^^^                            ^            ^^^
# not replaced                replaced        not replaced

Without word boundaries: (Demo)

echo preg_replace(
         '/.*\K' . preg_quote($search, '/') . '/i',
         $replace,
         $subject
     );

Output:

  The Quick Brown Fox Jumps Over The Lazy Dog's Armos!
# ^^^                            ^^^            ^
# not replaced              not replaced        replaced

2 Comments

Don't confuse strings with words. Words are space-delimited strings, strings can be parts of words. The right code should convert "Thermos" to "Aermos" and leave "The Lazy" untouched.
Yes. I am deliberately using word boundaries although not asked for. There was no minimal reproducible example from the OP so we won't know if case sensitivity and word boundaries are necessary. I am being generous and offering several new techniques to the page which researchers may want. I can extend my answer and add another snippet without boundaries if you like.
0

Below corrected zack's code (https://stackoverflow.com/a/11144999/9996503). Be careful with regular expressions! Consider this:

$string = 'Round brackets (parentheses) "()", square brackets "()"';
$find = '()';
$replace = '[]';
// Zack's code:
$result = preg_replace(strrev("/$find/"),strrev($replace),strrev($string),1);
var_dump($result); // Output: NULL
// Correct code:
$result = strrev(preg_replace('/'.preg_quote(strrev($find)).'/', strrev($replace), strrev($string), 1));
echo $result; //Output: Round brackets (parentheses) "()", square brackets "[]"

Comments

-1

Use the "$" on a reg expression to match the end of the string

$string = 'The quick brown fox jumps over the lazy fox';
echo preg_replace('/fox$/', 'dog', $string);

//output
'The quick brown fox jumps over the lazy dog'

2 Comments

this works only if the last occurence is in the end of the string ideone.com/nbNSNq
This will not work if any other characters appear after the last 'fox'.
-1

For the interested: I've written a function that utilises preg_match so that you're able to replace from right hand side using regex.

function preg_rreplace($search, $replace, $subject) {
    preg_match_all($search, $subject, $matches, PREG_SET_ORDER);
    $lastMatch = end($matches);

    if ($lastMatch && false !== $pos = strrpos($subject, $lastMatchedStr = $lastMatch[0])) {
        $subject = substr_replace($subject, $replace, $pos, strlen($lastMatchedStr));
    }

    return $subject;
}

Or as a shorthand combination/implementation of both options:

function str_rreplace($search, $replace, $subject) {
    return (false !== $pos = strrpos($subject, $search)) ?
        substr_replace($subject, $replace, $pos, strlen($search)) : $subject;
}
function preg_rreplace($search, $replace, $subject) {
    preg_match_all($search, $subject, $matches, PREG_SET_ORDER);
    return ($lastMatch = end($matches)) ? str_rreplace($lastMatch[0], $replace, $subject) : $subject;
}

based on https://stackoverflow.com/a/3835653/3017716 and https://stackoverflow.com/a/23343396/3017716

1 Comment

The preg_ technique generates warnings and should not be used. 3v4l.org/lNU8R

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.