Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficient/simple way to replace every occurrence but the last occurence of a substring in a string (str_replace_except_last)?

Imagine the following inputs and the desired output after 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?

Want to perform the simple task of removing every occurrence of a character or substring but NOT the last occurrence. Basically this is what str_replace does, however it replaces any occurrence.

Hint: Made some experiments with substr_count however I did not find how to replace ocurrence number X in a string easily?

like image 618
Blackbam Avatar asked Aug 31 '16 14:08

Blackbam


3 Answers

I think this will be the most efficient/simple solution (however I didn't run it though some run-time test).

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;
}

Since the question was also related to efficiency, I decided to test the two versions (mine and the one offered by @Don'tPanic, which is based on arrays).

First - Let me just say that premature optimization is the root of all evil

Now that we are done with that we can move next :)

I decided to create a random string of 10M chars, the string will also contain . (which will be our needle).

I run the two functions 1000 times on same string and checked the average time it took each function to run.
I also checked how much memory each function took to work.

Here are the results:

  1. The creation of the 10M characters string took 2.3 seconds.
  2. The string function run on average 0.016 second (each iteration)
    The memory usage was 9.8M
  3. The array function run on average 0.049 second (each iteration)
    The memory usage was 45.1M

Here is the complete code (if you want to run it yourself):

$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";

(I commented out the output of the memory usage inside the functions, if you want it you can remove the comment).

like image 102
Dekel Avatar answered Oct 30 '22 14:10

Dekel


Split the main string on the string to replace (except for the last piece), then join it back together with the replacement.

function str_replace_except_last($replace, $replacement, $text) {
    $array = explode($replace, $text, substr_count($text, $replace));
    return implode($replacement, $array);
}
like image 41
Don't Panic Avatar answered Oct 30 '22 14:10

Don't Panic


You could use preg_replace to remove every occurrence except the last. The regex does a look ahead and only replaces if the pattern also exists later in the string.

$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);
}
like image 1
Surberus Avatar answered Oct 30 '22 14:10

Surberus