替换字符串中最后一次出现的某个子串。

174

有人知道一种非常快速的方法来替换字符串中最后一个出现的字符串为另一个字符串吗?

请注意,该字符串的最后一个出现可能不是字符串的最后字符。

示例:

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

预期输出:

The Quick Brown Fox Jumps Over A Lazy Dog

你可能会发现s($str)->replaceLast($search, $replace)有所帮助,这在此独立库中可以找到。 - caw
15个回答

264
你可以使用这个函数:
function str_lreplace($search, $replace, $subject)
{
    $pos = strrpos($subject, $search);

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

    return $subject;
}

5
@Jason无论如何它都不会返回TRUE,它总是返回一个字符串。如果无法进行替换,它将返回原始的$subject,就像substr_replacestr_replace一样。 - Mischa
@Mischa 在这种情况下不是一样的吗?我试图做类似于!str_lreplace的事情,但如果它不返回false,那么它被认为是true,对吧?无论如何,这帮了我很多,我很感激。谢谢。 - Jazzy
2
这怎么可能?strpos - 在字符串中查找子字符串的第一个出现位置 - 编辑:哇。Php天才们真的创造了一个名为strposstrrpos的函数吗?谢谢... - BarryBones41
1
@Barry 这是 PHP 不应受到责备的情况之一 :-) 这些函数的命名模仿了几十年前标准 C 库中的 strstrstrrstr(https://linux.die.net/man/3/strrstr),这两个函数实际上是相同的功能。(但是他们一定要改名吗?) - alexis
如果我们只想替换最后一个字符,我们不需要使用strrpos函数,因为我们已经知道了位置。我们可以直接执行$position = strlen($subject) - 1;和if ($subject[$position] == $search),然后再使用substr_replace函数。 - baptx
显示剩余3条评论

32

另一个一行代码,但没有使用 preg 函数:

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

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

5
就此问题而言,被接受的解决方案比这个方案快大约35%。 - JustCarty
1
我总是会选择使用正则表达式函数而不是6个函数调用。我无法想象在专业代码中使用这种技术。 - mickmackusa

27
$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

为什么它适用于所有反转的字符串?使用正则表达式是否有某些(我假设)特定的性能优势? - Kamafeather
不,实际上它会降低性能,但这是因为你只想要最后一次出现,所以你将搜索限制为一个,并且如果你想要第一个,就不需要反转任何东西。 - Tofandel

20
下面这个相当简洁的解决方案使用了PCRE正向前瞻断言来匹配感兴趣的子字符串的最后一次出现,即不跟随任何其他同样子字符串的出现的子字符串。因此,该示例将last 'fox'替换为'dog'
$string = 'The quick brown fox, fox, fox jumps over the lazy fox!!!';
echo preg_replace('/(fox(?!.*fox))/', 'dog', $string);

输出: 

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

我不明白为什么这个后来的回答比Alix Axel的回答获得了更多的赞成票。相比Alix的模式,这个模式需要3倍的步骤。负向先行断言是一种不必要的开销,因为它并没有像贪婪点匹配那样提高准确性。 - mickmackusa

13

你可以这样做:

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

结果是 'Hello John';


5
只要没有重复字符,这个方法就有效。在我的情况下,我正在从归档日期中删除页面编号,因此得到的是“2015-12/2”,它会将所有的斜杠和数字 2 都去掉,变成了“2015-1”。 - Mike
1
只有在最后一个被搜索的出现是最后一个单词且其后没有其他字符时,此方法才有效。 - AwesomeGuy
1
这段代码无法正常工作,因为rtrim的行为与你想象的不同。它会从字符串末尾删除任何按照搜索字符串中出现的字符(并始终附加替换字符串),例如:"Hello word" -> "Hello John","Hello lord" -> "Hello John","Hello motor" -> "Hello motJohn","Hello worldy" -> "Hello worldyJohn"。 - Jake
4
我不建议任何人使用这个(没有解释的)代码,因为它容易悄悄地失败。这个答案并没有尝试“找到”字符串的最后一个出现位置——它只是在字符掩码满足的情况下从输入字符串的右侧消耗字符。这个做法和 rtrim($str, 'dlorw') 是一样的。 - mickmackusa

10
这是一个古老的问题,但为什么大家都忽视了最简单的基于正则表达式的解决方案呢?普通的正则表达式量词都是贪婪的!如果你想找到模式的最后一个实例,只需在其前面添加.*即可。以下是做法示例:
$text = "The quick brown fox, fox, fox, fox, jumps over etc.";
$fixed = preg_replace("((.*)fox)", "$1DUCK", $text);
print($fixed);

这将替换最后一个实例的单词 "fox" 为 "DUCK",这是预期的结果,并会打印出来:
The quick brown fox, fox, fox, DUCK, jumps over etc.

1
谢谢!完美的函数来包装我的表达式以完成这个任务。在我的情况下,我要用", and "替换最后一个逗号。很高兴我在这个答案列表里往下滚了一点。 - rlhane
你“忽略了”最简单的基于正则表达式的解决方案--这个技巧在8年前就被Alix Axel在此页面上发布了。他/她的答案是相同的,只不过他/她的代码片段是动态的。你的答案将针头硬编码到搜索模式中,并将替换字符串硬编码到替换参数中。我更进一步地简化了替换,使用了\K字符在这里 - mickmackusa
@mickmackusa,你说得对,发现得好。确实,Alix Axel的答案在左侧使用了贪婪搜索...但是让我们只是说我不认为它特别“简单”:-D。(而且没有理由使用~作为分隔符...这只会让事情更加混乱。) - alexis
我认为您的括号分隔符对我而言更加令人困惑——尽管我很熟悉阅读正则表达式。即使模式中没有斜杠,我经常使用波浪线作为分隔符——这是我的个人喜好。看到模式中的括号,我的大脑会认为这是一个捕获组或环视。Alix的答案之所以较不简单,主要是因为它是动态的。我的答案稍微紧凑了一些语法。 - mickmackusa
括号是一个捕获组。 - alexis

7
这也可以起作用:
function str_lreplace($search, $replace, $subject)
{
    return preg_replace('~(.*)' . preg_quote($search, '~') . '(.*?)~', '$1' . $replace . '$2', $subject, 1);
}

更新 稍微更简洁的版本 (http://ideone.com/B8i4o):

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

我做错了吗?如果是的话,请忽略我 :) ||| echo str_lreplace("x", "y", "this x or that x"); => 输出:"y" 参见:http://www.ideone.com/UXuTo - edorian
@edorian:糟糕!抱歉,我匆忙发布了那个,正确的版本在这里:http://ideone.com/vR073。 - Alix Axel
本答案缺少教育性解释。 - mickmackusa

3
$string = "picture_0007_value";
$findChar =strrpos($string,"_");
if($findChar !== FALSE) {
  $string[$findChar]=".";
}

echo $string;

除了代码中的错误,Faruk Unal 给出了最好的答案。一个函数就能解决问题。


你需要检查 $findChar 是否不为 false(与被接受的答案相同)。如果字符串不包含搜索的字符串,你会收到通知并替换第一个字符。 - shaggy
这很不错,但现在它只能用一个字符替换另一个字符。 - Pete
以上评论是正确的。这仅在替换长度为一的单字节字符字符串时才是正确的。 - mickmackusa

3

您可以使用strrpos()函数来查找最后一个匹配项。

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

$string[$findChar]=".";

echo $string;

输出:picture_0007.value


2
只有在替换长度为1且搜索字符存在的单字节字符字符串时,此方法才是正确的--在所有其他情况下,它都会失败。 - mickmackusa

2
答案接受的速记方式
function str_lreplace($search, $replace, $subject){ 
    return is_numeric($pos=strrpos($subject,$search))?
    substr_replace($subject,$replace,$pos,strlen($search)):$subject;
}

这个代码不如被接受的答案易读。为什么不使用更好的编码规范呢? 请阅读PSR编码标准。 - mickmackusa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接