2016-08-31 16 views
5

Wyobraźmy sobie następujące wejścia i pożądany wynik po str_replace_except_last($replace_except_last,$replacement,$text):Wydajny/prosty sposób na zastąpienie każdego wystąpienia, ale ostatnie wystąpienie podciągu w ciągu znaków (str_replace_except_last)?

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? 

Chcesz wykonać proste zadanie usuwania każde wystąpienie znaku lub podciąg, ale nie ostatnie wystąpienie. Zasadniczo to właśnie robi str_replace, ale zastępuje dowolne wystąpienie.

Podpowiedź: Przeprowadziłem pewne eksperymenty z substr_count, ale nie znalazłem, jak łatwo zastąpić numer przewyższenia X w łańcuchu?

+1

@ Blackackam, Jeśli jesteś zainteresowany, dodałem informacje o wydajności w mojej odpowiedzi. – Dekel

+0

Taka bardzo interesująca intuicja podpowiadała mi, że prawdopodobnie macierzyste funkcje PHP na łańcuchach mogłyby działać nieco lepiej niż tablice (to prawdopodobnie ma znaczenie tylko wtedy, gdy funkcja jest zapętlana bardzo często ;-) Może wynik nie jest tak jasny w porównaniu do regexu rozwiązanie? Mogę przetestować później. – Blackbam

+1

Regex zwykle wymaga więcej czasu do uruchomienia (a także większej ilości pamięci). Funkcja regex przez @Surberus trwała 20 M i ~ 0,03 s (dwa razy więcej niż czas funkcji łańcuchowej, nieco ponad połowę czasu funkcji tablicowej). – Dekel

Odpowiedz

4

Myślę, że będzie to najbardziej wydajne/proste rozwiązanie (jednak nie uruchomiłem go przez jakiś test w czasie wykonywania).

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

Ponieważ pytanie było również związane z wydajnością, postanowiłem przetestować dwie wersje (kopalnia i ten oferowany przez @ Don'tPanic, która jest oparta na tablicach).

pierwsze - Powiem tylko, że przedwczesna optymalizacja jest źródłem wszelkiego zła

Teraz, gdy skończysz, że możemy przejść obok :)

postanowiłem stworzyć losowy ciąg znaków 10M, łańcuch będzie również zawierał . (który będzie naszą igłą).

Uruchomiłem dwie funkcje 1000 razy na tym samym łańcuchu i sprawdziłem średni czas, w którym każda z funkcji była uruchamiana.
Sprawdziłem również, ile pamięci zajęła każda funkcja.

Oto wyniki:

  1. Stworzenie łańcucha znaków 10M trwało 2,3 sekundy.
  2. Przebieg funkcja łańcuch o średniej 0,016 sekundę (iteracji)
    Wykorzystanie pamięci została 9,8
  3. Przebieg funkcja tablicy średnio 0,049 sekundę (kolejnych iteracjach)
    Wykorzystanie pamięci została 45.1M

Oto kompletny kod (jeśli chcesz uruchomić go samodzielnie):

$ITERATIONS = 1000; 

function generateRandomString($length = 10) { 
    $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ. '; 
    $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 wykomentowane wyjściu użycia pamięci wewnętrznej funkcji, jeśli chcesz go co możliwe usuń komentarz).

+0

Testowane tutaj (http://sandbox.onlinephpfunctions.com/) wydaje się działać świetnie. – Blackbam

+0

@ Blacklam, wiem, że to działa, po prostu nie jestem pewien, czy to jest najbardziej efektywny sposób :) to wszystko – Dekel

+0

Myślę, że to, metoda tablicy lub wyrażenie regularne powinny być na tyle podobne pod względem wydajności, że _nie_ przy użyciu cokolwiek preferowana metoda nie byłaby optymalną wartością. Podobają mi się również funkcje ciągów. –

3

Podziel główny ciąg na łańcuchu, aby go zastąpić (z wyjątkiem ostatniego elementu), a następnie połącz go z powrotem wraz z zamiennikiem.

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

Działa świetnie, ale ja wolę bez rozwiązania macierzowego ;-) – Blackbam

+0

Dzięki! Do każdego z nich. Uwielbiam tablice. –

+0

@ Don'tPanic, fajne rozwiązanie. Mam też ode mnie głosowanie :) – Dekel

1

Możesz użyć preg_replace, aby usunąć każde wystąpienie oprócz ostatniego. Wyrażenie spogląda do przodu i zastępuje je tylko wtedy, gdy wzorzec istnieje również później w łańcuchu.

$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); 
} 
+0

Działa świetnie, ale wolę rozwiązanie bez regex ;-) – Blackbam

0

Dla twojego wymagania, myślę, że powinieneś użyć preg_replace, strripos i substr. Spróbuj poniżej kodu, łatwo go zrozumieć.

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

?> 

Mam nadzieję, że to zadziała, proszę o komentarz.

0
function replaceExceptLast($search, $replace, $string) { 
    // Search string not found or Only one occurence 
    if(strpos($string, $search) === false || substr_count($string, $search) <= 1) { 
     return $string; 
    } 

    $strExcpetLast = substr($string, 0, strrpos($string, $search)); 
    $lastOccurence = strrchr($string, $search); 
    return str_replace($search, $replace, $strExcpetLast) . $lastOccurence; 
}