【问题标题】:Efficient/simple way to replace every occurrence but the last occurence of a substring in a string (str_replace_except_last)?有效/简单的方法来替换字符串中每个出现的子字符串,但最后一次出现的子字符串(str_replace 除了最后一个)?
【发布时间】:2016-08-31 14:43:05
【问题描述】:

想象一下str_replace_except_last($replace_except_last,$replacement,$text)之后的以下输入和所需的输出:

func(".","",12.833331.3198912.980289012.92) => 128333313198912980289012.92
func(".","",31.0) => 31.0
func(".","",8) => 8
func(".","",9190.1.1.1....1.1.....1) => 919011111.1
func(".","",98909090....) => 98909090.
func("beer","","My beer is the best beer.") => My is the best beer.
func("it","fit,"Is it really it or is it not?") => Is fit really fit or is it not?

想要执行删除每个出现的字符或子字符串但不是最后一次出现的简单任务。基本上这就是str_replace 所做的,但它会替换任何出现的情况。

提示:用substr_count 做了一些实验,但是我没有找到如何轻松替换字符串中出现的数字 X?

【问题讨论】:

  • @Blackbam,如果您有兴趣,我在答案中添加了性能信息。
  • Thx 是的,非常有趣的直觉告诉我,字符串上的 PHP 本机函数可能会比数组执行得更好一些(这可能仅在函数经常循环时才重要;-) 也许结果不是与正则表达式解决方案相比是否清楚?我可以稍后测试。
  • Regex 通常需要更多时间来运行(也需要更多内存)。 @Surberus 的正则表达式函数耗时 20M 和 ~0.03 秒(字符串函数时间的两倍,数组函数时间的一半多一点)。

标签: php string function


【解决方案1】:

我认为这将是最有效/最简单的解决方案(但是我没有通过一些运行时测试运行它)。

function str_replace_except_last($needle, $replace, $text) {
    if ($last_pos = strrpos($text, $needle)) {
        $text = str_replace($needle, $replace, substr($text, 0, $last_pos)) . substr($text, $last_pos);
    }
    return $text;
}

由于问题也与效率有关,我决定测试两个版本(我的版本和@Don'tPanic 提供的版本,它基于数组)。

首先 - 我只想说过早的优化是万恶之源

现在我们已经完成了,我们可以下一步了:)

我决定创建一个 10M 字符的随机字符串,该字符串还将包含 .(这将是我们的针)。

我在同一个字符串上运行这两个函数 1000 次,并检查了每个函数运行的平均时间。
我还检查了每个函数需要多少内存才能工作。

结果如下:

  1. 10M 字符串的创建耗时 2.3 秒。
  2. 字符串函数平均运行 0.016 秒(每次迭代)
    内存使用量为 9.8M
  3. 数组函数平均运行 0.049 秒(每次迭代)
    内存使用量为 45.1M

这是完整的代码(如果你想自己运行的话):

$ITERATIONS = 1000;

function generateRandomString($length = 10) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ. ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}

function func_str($needle, $replace, $text) {
    $m1 = memory_get_usage();

    if ($last_pos = strrpos($text, $needle)) {
        $text = str_replace($needle, $replace, substr($text, 0, $last_pos)) . substr($text, $last_pos);
    }

    $m2 = memory_get_usage();
    //echo "memory diff ". ($m2-$m1) ."\n";

    return $text;
}

function func_arr($needle, $replace, $text) {
    $m1 = memory_get_usage();

    $array = explode($replace, $text, substr_count($text, $replace));
    $text = implode($replacement, $array);
    $m2 = memory_get_usage();
    //echo "memory diff ". ($m2-$m1) ."\n";

    return $text;
}

$m1 = memory_get_usage();
$s = microtime(true);
$str1 = generateRandomString(10000000);
$e = microtime(true);
echo "create took ". ($e-$s) ." seconds\n";
echo "Number of occurances: " . substr_count($str1, '.') . "\n";

$s = microtime(true);
for ($i = 0; $i < $ITERATIONS; $i++) {
    func_str(".","",$str1);
}
$e = microtime(true);
echo "remove took ". ($e-$s) ." seconds, avg: ". ($e-$s)/$ITERATIONS ."\n";

$s = microtime(true);
for ($i = 0; $i < $ITERATIONS; $i++) {
    func_arr(".","",$str1);
}
$e = microtime(true);
echo "remove took ". ($e-$s) ." seconds, avg: ". ($e-$s)/$ITERATIONS ."\n";

(我把函数内部内存使用的输出注释掉了,如果你想要的话可​​以去掉注释)。

【讨论】:

  • @Blackbam,我知道它有效,我只是不确定这是最有效的方法 :) 仅此而已
  • 我认为这个、数组方法或正则表达式在性能方面应该足够相似,使用您喜欢的任何方法都不值得优化。我也喜欢仅使用字符串函数的方法。
  • @Don'tPanic,我认为如果我们谈论性能-explode 功能将不太有效并且需要更多时间(您需要使用新元素创建一个新数组,并且您需要分配每个元素的内存等等)。我将尝试进行快速性能测试并发布结果:)
  • @Don'tPanic,我添加了性能信息如果你想要一个 loko
【解决方案2】:

在要替换的字符串上拆分主字符串(最后一段除外),然后将其与替换部分重新连接。

function str_replace_except_last($replace, $replacement, $text) {
    $array = explode($replace, $text, substr_count($text, $replace));
    return implode($replacement, $array);
}

【讨论】:

  • 效果很好但是我更喜欢没有数组的解决方案;-)
  • 谢谢!各有各的。我有点喜欢数组。
  • @Don'tPanic,不错的解决方案。也得到了我的投票:)
  • 谢谢!在我想起限制爆炸后,我更喜欢它了。
【解决方案3】:

您可以使用 preg_replace 删除除最后一个之外的所有匹配项。正则表达式会向前看,仅当模式也存在于字符串后面时才会替换。

$str = '66.768.876876.8.7876';
$pattern = '.';

echo(str_replace_except_last($pattern, '', $str));

function str_replace_except_last($replace_except_last, $replacement, $text)
{
    $pattern = preg_quote($replace_except_last);
    return preg_replace('/' . $pattern . '(?=[^' . $pattern . ']*' . $pattern . '[^' . $pattern . ']*)/', $replacement, $text);
}

【讨论】:

  • 效果很好但是我更喜欢没有正则表达式的解决方案;-)
【解决方案4】:

根据您的要求,我认为您应该使用 preg_replace、strripos 和 substr。请尝试下面的代码,很容易理解。

<?php

    function str_replace_without_last($text,$text_to_replace,$text_to_replace_with)
    {

        $text_position = strripos($text,$text_to_replace);
        $suffix_text = substr($text,$text_position);
        $prefix_text = substr($text,0,$text_position);
        $text_to_replace_with = $text_to_replace_with;
        $number_of_text_occurences = substr_count($prefix_text, $text_to_replace);
        $prefix = preg_replace("/$text_to_replace/",$text_to_replace_with,$prefix_text);
        $result = $prefix.$suffix_text;
        return $result;

    }

    // Call function for testing

    $text = "My beer is the best beer and good beer.";
    $text_to_replace = "beer";
    $text_to_replace_with = "";
    $result = str_replace_without_last($text,$text_to_replace,$text_to_replace_with);
    echo $result;

?>

希望对你有用,欢迎评论。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-19
    • 1970-01-01
    • 2011-07-26
    • 2014-06-13
    • 2015-05-11
    • 2022-01-06
    相关资源
    最近更新 更多