Używasz str_replace, aby działał tylko przy pierwszym dopasowaniu?


Odpowiedzi:


346

Można to zrobić za pomocą preg_replace :

function str_replace_first($from, $to, $content)
{
    $from = '/'.preg_quote($from, '/').'/';

    return preg_replace($from, $to, $content, 1);
}

echo str_replace_first('abc', '123', 'abcdef abcdef abcdef'); 
// outputs '123def abcdef abcdef'

Magia tkwi w opcjonalnym czwartym parametrze [Limit]. Z dokumentacji:

[Limit] - Maksymalna możliwa zamiana dla każdego wzoru w każdym ciągu tematycznym. Domyślnie -1 (bez limitu).


Jednak zobacz odpowiedź zombata na bardziej wydajną metodę (z grubsza, 3-4x szybciej).


39
Minusem tej metody jest obniżenie wydajności wyrażeń regularnych.
zombat

27
Kolejnym minusem jest to, że musisz użyć preg_quote () na „igle” i uciec meta-znaków $ i \ w zamianie.
Josh Davis,

32
Nie udaje się to jako ogólne rozwiązanie z powodu nieprzyjemnych problemów związanych z ucieczką.
Jeremy Kauffman,

2
Zdecydowanie zbyt często wyrażenia regularne są odrzucane z powodu „wydajności”, gdyby wydajność była głównym problemem, nie pisalibyśmy w PHP! Do zawinięcia wzoru można użyć czegoś innego niż „/”, na przykład „~”, co pomogłoby w pewnym stopniu uniknąć problemu ucieczki. Zależy, jakie są dane i skąd pochodzą.
ThomasRedstone,

1
Pomijając wady wydajności - czy ci, którzy narzekają na unikanie problemów, mają na myśli coś konkretnego oprócz potencjalnych błędów preg_quote? Na przykład @ThomasRedstone martwi się, że separator /może być niebezpieczny, jeśli się pojawi $from, ale na szczęście tak nie jest: jest poprawnie uciekany z powodu preg_quotedrugiego parametru (można to łatwo przetestować). Chciałbym usłyszeć o konkretnych problemach (które mogłyby stanowić poważne błędy bezpieczeństwa PCRE w mojej książce).
MvanGeest,

610

Nie ma żadnej wersji, ale rozwiązanie wcale nie jest hakujące.

$pos = strpos($haystack, $needle);
if ($pos !== false) {
    $newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
}

Całkiem proste i oszczędza karę wydajności wyrażeń regularnych.


Premia: Jeśli chcesz zastąpić ostatnie wystąpienie, po prostu użyj strrposzamiast strpos.


17
Może być znacznie szybszy i zużywa mniej pamięci niż wyrażenia regularne. Nie mam pojęcia, dlaczego ktoś miałby tak głosować ...
Josh Davis,

12
Podoba mi się to podejście, ale kod zawiera błąd, ostatnim parametrem wywołania substr_replace powinien być strlen ($ igła) zamiast strlen ($ replace) .. uwaga na to !!
Nelson

Jest „hacky” w tym sensie, że potrzeba znacznie więcej czasu, aby dowiedzieć się, co się dzieje. Również jeśli byłby to czysty kod, nie wspominano by, że kod zawiera błąd. Jeśli można popełnić błąd w tak małym fragmencie, jest już zbyt hackerski.
Camilo Martin

9
Nie zgadzam się z @CamiloMartin w odniesieniu do liczby linii vs. możliwości błędów. Chociaż substr_replacejest to dość niewygodna funkcja ze względu na wszystkie parametry, prawdziwym problemem jest to, że manipulowanie ciągami liczbowymi jest czasami trudne - musisz uważać, aby przekazać odpowiednią zmienną / offset do funkcji. I tak posunąłbym się do stwierdzenia, że ​​powyższy kod jest najprostszym, i dla mnie, logicznym, podejściem.
Alex

1
Genialne podejście. Działa doskonale, zastępując wartości zmiennych, które mają w nich zarezerwowane znaki regularne (więc preg_replace jest opatrzony). To jest proste i eleganckie.
Praesagus

96

Edycja: obie odpowiedzi zostały zaktualizowane i są teraz poprawne. Pozostawię odpowiedź, ponieważ czasy funkcji są nadal przydatne.

Odpowiedzi „zombat” i „za dużo php” są niestety nieprawidłowe. To jest korekta opublikowanej odpowiedzi zombat (ponieważ nie mam wystarczającej reputacji, aby opublikować komentarz):

$pos = strpos($haystack,$needle);
if ($pos !== false) {
    $newstring = substr_replace($haystack,$replace,$pos,strlen($needle));
}

Zanotuj strlen ($ igła), zamiast strlen ($ replace). Przykład Zombata będzie działał poprawnie tylko wtedy, gdy igła i zamiennik będą tej samej długości.

Oto ta sama funkcjonalność w funkcji z tym samym podpisem, co własny str_replace PHP:

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

Oto poprawiona odpowiedź „za dużo php”:

implode($replace, explode($search, $subject, 2));

Zwróć uwagę na 2 na końcu zamiast 1. Lub w formacie funkcji:

function str_replace_first($search, $replace, $subject) {
    return implode($replace, explode($search, $subject, 2));
}

Zmierzyłem czas obu funkcji, a pierwsza jest dwa razy szybsza, gdy nie znaleziono dopasowania. Są takie same prędkości po znalezieniu dopasowania.


Dlaczego nie uogólnić tego na: str_replace_fiętkie (mieszane $ s, mieszane $ r, int $ offset, int $ limit), gdzie funkcja zastępuje wystąpienia $ limit zaczynające się od dopasowania $ offset (n-ty).
Adam Friedman

Szkoda, że ​​dotyczy to tylko rozróżniania wielkości liter.
andreszs

4
@Andrew stripos()na ratunek :-)
Gras Double

76

Zastanawiałem się, który z nich był najszybszy, więc przetestowałem je wszystkie.

Poniżej znajdziesz:

  • Pełna lista wszystkich funkcji, które zostały dodane na tę stronę
  • Testy porównawcze dla każdego zanieczyszczenia (średni czas wykonania ponad 10 000 przebiegów)
  • Linki do każdej odpowiedzi (dla pełnego kodu)

Wszystkie funkcje zostały przetestowane przy tych samych ustawieniach:

$string = 'OOO.OOO.OOO.S';
$search = 'OOO'; 
$replace = 'B';

Funkcje, które zastępują tylko pierwsze wystąpienie ciągu w ciągu:


Funkcje, które zastępują tylko ostatnie wystąpienie ciągu w ciągu:


Dzięki za to zazwyczaj używam preg_replace, ponieważ jest najbardziej elastyczny, jeśli przyszłe poprawki są wymagane w większości przypadków 27% wolniej nie będzie znaczące
zzapper

@oLinkWebDevelopment Chciałbym zobaczyć twój skrypt testowy. Myślę, że może się to przydać.
Dave Morton,

Powód, dla którego substr_replace()wygrywa wynik, jest prosty; ponieważ jest to funkcja wewnętrzna. Dwie funkcje wewnętrzne i funkcje zdefiniowane przez użytkownika różnią się pod względem wydajności, ponieważ wewnętrzna działa w niższych warstwach. Dlaczego więc nie preg_match()? Wyrażenia regularne są prawie wolniejsze niż każda wewnętrzna funkcja manipulacji ciągiem, ze względu na ich naród wyszukiwania w ciągu wiele razy.
MAChitgarha,

1
Mam nadzieję, że punkt odniesienia dla twojego „zwycięzcy” ( substr_replace($string, $replace, 0, strlen($search));) nie tylko napisał to statyczne 0. Częścią splotu rozwiązań innych niż wyrażenia regularne jest to, że muszą „znaleźć” punkt wyjścia, zanim będą wiedzieć, gdzie wymienić.
mickmackusa,

55

Niestety nie znam żadnej funkcji PHP, która mogłaby to zrobić.
Możesz rzucić własnym dość łatwo w ten sposób:

function replace_first($find, $replace, $subject) {
    // stolen from the comments at PHP.net/str_replace
    // Splits $subject into an array of 2 items by $find,
    // and then joins the array with $replace
    return implode($replace, explode($find, $subject, 2));
}

Myślę, że jest to najbardziej golfowa wersja ze wszystkich - używająca joinzamiast implode.
Tytus

return implode($replace, explode($find, $subject, $limit+1));dla niestandardowych numerów wymiany
beppe9000,

7

Stworzyłem tę małą funkcję, która zamienia ciąg znaków na ciąg znaków (wielkość liter ma znaczenie) z limitem, bez potrzeby Regexp. To działa dobrze.

function str_replace_limit($search, $replace, $string, $limit = 1) {
    $pos = strpos($string, $search);

    if ($pos === false) {
        return $string;
    }

    $searchLen = strlen($search);

    for ($i = 0; $i < $limit; $i++) {
        $string = substr_replace($string, $replace, $pos, $searchLen);

        $pos = strpos($string, $search);

        if ($pos === false) {
            break;
        }
    }

    return $string;
}

Przykładowe użycie:

$search  = 'foo';
$replace = 'bar';
$string  = 'foo wizard makes foo brew for evil foo and jack';
$limit   = 2;

$replaced = str_replace_limit($search, $replace, $string, $limit);

echo $replaced;
// bar wizard makes bar brew for evil foo and jack

Chociaż wolałbym to zrobić, ===falsezamiast is_bool(być bardziej wyraźnym - kciuk w górę, tylko dlatego, że udało mi się uniknąć szaleństwa RegExp ! ... a jednocześnie działa i czyste rozwiązanie ...
jave.web

Preferowanie łatwego do dostosowania preg_rozwiązania nie jest szaleństwem, ale osobistą preferencją. return preg_replace('/'.preg_quote($search, '/').'/', $replace, $content, 1);jest dość łatwy do odczytania dla osób, które nie boją się wyrażeń regularnych. Potrzebujesz wyszukiwania bez rozróżniania wielkości liter? Dodaj ipo ograniczniku wzoru końcowego. Potrzebujesz obsługi Unicode / Multibyte? Dodaj upo ograniczniku wzoru końcowego. Potrzebujesz obsługi granicy słów? Dodaj \bpo obu stronach szukanego ciągu. Jeśli nie chcesz wyrażenia regularnego, nie używaj wyrażenia regularnego. Konie na kursy, ale na pewno nie szaleństwo.
mickmackusa,

3

Najprostszym sposobem byłoby użycie wyrażenia regularnego.

Innym sposobem jest znalezienie pozycji ciągu za pomocą strpos (), a następnie substr_replace ()

Ale naprawdę wybrałbym RegExp.


Ta „wskazówka” jest raczej niejasna / ma niską wartość w porównaniu do innych postów na tej stronie.
mickmackusa,

3
function str_replace_once($search, $replace, $subject) {
    $pos = strpos($subject, $search);
    if ($pos === false) {
        return $subject;
    }

    return substr($subject, 0, $pos) . $replace . substr($subject, $pos + strlen($search));
}

Odpowiedzi zawierające tylko kod mają niską wartość w StackOverflow, ponieważ źle radzą sobie z edukowaniem / wyposażaniem tysięcy przyszłych badaczy.
mickmackusa,

3

=> KOD ZOSTAŁ ZMIENIONY, więc uważaj niektóre komentarze za stare

I dziękuję wszystkim za pomoc w ulepszeniu tego

Wszelkie BŁĘDY, proszę o poinformowanie mnie; Zaraz to naprawię

Więc chodźmy na:

Zamiana pierwszego „o” na „ea” na przykład:

$s='I love you';
$s=str_replace_first('o','ea',$s);
echo $s;

//output: I leave you

Funkcja:

function str_replace_first($a,$b,$s)
         {
         $w=strpos($s,$a);
         if($w===false)return $s;
         return substr($s,0,$w).$b.substr($s,$w+strlen($a));
         }

Nie powiedzie się, jeśli $ to powtarzało się znaki takie jak aaa vs aaaaaaaaa
Cristo

Myślę, że powinno być substr($where,$b+strlen($this)), nie substr($where,$b+1). I myślę, że substr_replaceto jest szybsze.
Tytus

Kod został zmieniony, teraz działa nawet dla długich łańcuchów
PYK

To rozwiązanie nie działa w sposób zakodowany. Dowód: 3v4l.org/cMeZj A kiedy naprawisz problem nazewnictwa zmiennych, nie działa to, gdy szukana wartość nie zostanie znaleziona - uszkadza łańcuch wejściowy. Dowód: 3v4l.org/XHtfc
mickmackusa

Czy to jest sprawiedliwe, jeśli ktoś prosi o USUNIĘCIE kodu? @mickmackusa Czy możesz to jeszcze raz sprawdzić?
PYK

2
$string = 'this is my world, not my world';
$find = 'world';
$replace = 'farm';
$result = preg_replace("/$find/",$replace,$string,1);
echo $result;

To jest tak samo jak pierwsza odpowiedź. Poza tym, trzeba zrobić preg_quotez $findprzed użyciem go jako wyrazu.
Emil Vikström

tego właśnie użyłem, więc podniosłem głos. Pierwsza odpowiedź spowodowała konflikt z Drupalem, musiał on nadpisać funkcję pomocniczą drupala. Więc właśnie wziąłem kod, który był wewnątrz funkcji i użyłem go w jednej linii z resztą kodu ...
Dan Mantyla,

Ta odpowiedź tylko do kodu zawiera zbędne porady na stronie (nie wspominając o jej braku preg_quote(). Tę późno zduplikowaną odpowiedź można bezpiecznie usunąć ze strony, ponieważ jej porady są udzielane przez wcześniejszą i wyżej ocenianą akceptowaną odpowiedź.
mickmackusa

2

Aby rozwinąć odpowiedź @ renocor , napisałem funkcję, która jest w 100% kompatybilna wstecz str_replace(). Oznacza to, że można zastąpić wszystkie wystąpienia str_replace()ze str_replace_limit()nie brudząc się niczego, nawet tych z wykorzystaniem tablic dla $search, $replacei / lub$subject .

Funkcja może być całkowicie samodzielna, jeśli chcesz zastąpić wywołanie funkcji ($string===strval(intval(strval($string)))), ale odradzam to, ponieważ valid_integer()jest to dość przydatna funkcja w przypadku liczb całkowitych podanych jako łańcuchy.

Uwaga: Gdy tylko jest to możliwe, str_replace_limit()użyje str_replace()zamiast tego, więc wszystkie połączenia z str_replace()można zastąpićstr_replace_limit() bez obawy o obniżenie wydajności.

Stosowanie

<?php
$search = 'a';
$replace = 'b';
$subject = 'abcabc';
$limit = -1; // No limit
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2 zamienniki - bbcbbc

$limit = 1; // Limit of 1
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

1 zamienniki - bbcabc

$limit = 10; // Limit of 10
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2 zamienniki - bbcbbc

Funkcjonować

<?php

/**
 * Checks if $string is a valid integer. Integers provided as strings (e.g. '2' vs 2)
 * are also supported.
 * @param mixed $string
 * @return bool Returns boolean TRUE if string is a valid integer, or FALSE if it is not 
 */
function valid_integer($string){
    // 1. Cast as string (in case integer is provided)
    // 1. Convert the string to an integer and back to a string
    // 2. Check if identical (note: 'identical', NOT just 'equal')
    // Note: TRUE, FALSE, and NULL $string values all return FALSE
    $string = strval($string);
    return ($string===strval(intval($string)));
}

/**
 * Replace $limit occurences of the search string with the replacement string
 * @param mixed $search The value being searched for, otherwise known as the needle. An
 * array may be used to designate multiple needles.
 * @param mixed $replace The replacement value that replaces found search values. An
 * array may be used to designate multiple replacements.
 * @param mixed $subject The string or array being searched and replaced on, otherwise
 * known as the haystack. If subject is an array, then the search and replace is
 * performed with every entry of subject, and the return value is an array as well. 
 * @param string $count If passed, this will be set to the number of replacements
 * performed.
 * @param int $limit The maximum possible replacements for each pattern in each subject
 * string. Defaults to -1 (no limit).
 * @return string This function returns a string with the replaced values.
 */
function str_replace_limit(
        $search,
        $replace,
        $subject,
        &$count,
        $limit = -1
    ){

    // Set some defaults
    $count = 0;

    // Invalid $limit provided. Throw a warning.
    if(!valid_integer($limit)){
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting an '.
                'integer', E_USER_WARNING);
        return $subject;
    }

    // Invalid $limit provided. Throw a warning.
    if($limit<-1){
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
                'a positive integer', E_USER_WARNING);
        return $subject;
    }

    // No replacements necessary. Throw a notice as this was most likely not the intended
    // use. And, if it was (e.g. part of a loop, setting $limit dynamically), it can be
    // worked around by simply checking to see if $limit===0, and if it does, skip the
    // function call (and set $count to 0, if applicable).
    if($limit===0){
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
                'a positive integer', E_USER_NOTICE);
        return $subject;
    }

    // Use str_replace() whenever possible (for performance reasons)
    if($limit===-1){
        return str_replace($search, $replace, $subject, $count);
    }

    if(is_array($subject)){

        // Loop through $subject values and call this function for each one.
        foreach($subject as $key => $this_subject){

            // Skip values that are arrays (to match str_replace()).
            if(!is_array($this_subject)){

                // Call this function again for
                $this_function = __FUNCTION__;
                $subject[$key] = $this_function(
                        $search,
                        $replace,
                        $this_subject,
                        $this_count,
                        $limit
                );

                // Adjust $count
                $count += $this_count;

                // Adjust $limit, if not -1
                if($limit!=-1){
                    $limit -= $this_count;
                }

                // Reached $limit, return $subject
                if($limit===0){
                    return $subject;
                }

            }

        }

        return $subject;

    } elseif(is_array($search)){
        // Only treat $replace as an array if $search is also an array (to match str_replace())

        // Clear keys of $search (to match str_replace()).
        $search = array_values($search);

        // Clear keys of $replace, if applicable (to match str_replace()).
        if(is_array($replace)){
            $replace = array_values($replace);
        }

        // Loop through $search array.
        foreach($search as $key => $this_search){

            // Don't support multi-dimensional arrays (to match str_replace()).
            $this_search = strval($this_search);

            // If $replace is an array, use the value of $replace[$key] as the replacement. If
            // $replace[$key] doesn't exist, just an empty string (to match str_replace()).
            if(is_array($replace)){
                if(array_key_exists($key, $replace)){
                    $this_replace = strval($replace[$key]);
                } else {
                    $this_replace = '';
                }
            } else {
                $this_replace = strval($replace);
            }

            // Call this function again for
            $this_function = __FUNCTION__;
            $subject = $this_function(
                    $this_search,
                    $this_replace,
                    $subject,
                    $this_count,
                    $limit
            );

            // Adjust $count
            $count += $this_count;

            // Adjust $limit, if not -1
            if($limit!=-1){
                $limit -= $this_count;
            }

            // Reached $limit, return $subject
            if($limit===0){
                return $subject;
            }

        }

        return $subject;

    } else {
        $search = strval($search);
        $replace = strval($replace);

        // Get position of first $search
        $pos = strpos($subject, $search);

        // Return $subject if $search cannot be found
        if($pos===false){
            return $subject;
        }

        // Get length of $search, to make proper replacement later on
        $search_len = strlen($search);

        // Loop until $search can no longer be found, or $limit is reached
        for($i=0;(($i<$limit)||($limit===-1));$i++){

            // Replace 
            $subject = substr_replace($subject, $replace, $pos, $search_len);

            // Increase $count
            $count++;

            // Get location of next $search
            $pos = strpos($subject, $search);

            // Break out of loop if $needle
            if($pos===false){
                break;
            }

        }

        // Return new $subject
        return $subject;

    }

}

4
trochę wzdęty, jeśli mnie zapytasz. Najbardziej „nienawidzę” tego rozwiązania, to obsługa błędów. Łamie skrypt, jeśli przekażesz niepoprawne wartości. Myślisz, że wygląda profesjonalnie, ale tak nie jest, zamiast błędu zamiast tego wyślij zawiadomienie lub ostrzeżenie. Lepiej jest pominąć bzdury, zamiast tego zwrócić wartość false lub zero i nigdy nie używać wstecznego śledzenia w takiej funkcji. Najlepszym rozwiązaniem jest to, że programista może zdecydować, co zrobić, gdy dane wyjściowe są nieprawidłowe / nieoczekiwane.
Codebeat

@Erwinus używa E_USER_WARNINGprzez cały czas, co jest ostrzeżenie , nie błąd . Śledzenie jest niezwykle przydatne, aby dowiedzieć się, jaki kod przekazuje nieprawidłowe dane do funkcji w pierwszej kolejności (co jest absolutnie niezbędne do śledzenia błędów w produkcji). Jeśli chodzi o powrót $subjectzamiast false/ nulllub zgłoszenie błędu, był to po prostu osobisty wybór dla mojego przypadku użycia. Aby dopasować str_replace()funkcjonalność, najlepszym rozwiązaniem byłoby użycie wykrywalnych błędów krytycznych (podobnie str_replace()jak w przypadku zamknięcia dwóch pierwszych argumentów).
0b10011

Ach, nie zauważyłem E_USER_WARNING, którego używasz, przepraszam za to. Problem ze zwróceniem tematu polega na tym, że poza funkcją nigdy nie widać, że coś było nie tak. To powiedziawszy, funkcja może być o połowę mniejsza, jeśli zrobisz to mądrzej (jest to możliwe). Po drugie, komentarze są w porządku, gdy wyjaśniają coś złożonego, ale niezbyt przydatne do prostych rzeczy, takich jak zwiększenie wartości. Ogólnie uważam, że jest to niepotrzebne ogromne. Używanie ostrzeżeń w środowisku produkcyjnym może również stanowić problem z bezpieczeństwem, gdy użyjesz tego kodu na serwerze, który domyślnie nie tłumi komunikatów w czasie wykonywania (logi).
Codebeat

@Erwinus Mówiłem dużo, jeśli chodzi o komentarze, ponieważ niektórzy ludzie nie rozumieją języka tak dobrze, jak inni, a komentarze mogą zawsze zostać usunięte przez tych, którzy go rozumieją. Jeśli znasz lepszy sposób na uzyskanie tego samego wyniku końcowego dla wszystkich przypadków na krawędziach, z pewnością zmień odpowiedź. A jeśli twoje środowisko produkcyjne nie
ukrywa

TL; DR Ten fragment jest tak rozdęty, że nie wyobrażam sobie wybierania go zamiast funkcji wyrażenia regularnego (nienawidzę przewijania). Jeśli chcesz policzyć dokonane zamiany, w parametrze znajduje się parametr preg_replace(). Ponadto preg_replace()/ regex oferuje obsługę granicy słów (jeśli jest to pożądane) - coś, czego funkcje inne niż wyrażenia regularne nie zapewnią elegancko.
mickmackusa

2

Zgodnie z moim wynikiem testu chciałbym głosować na regular_express dostarczony przez karim79. (Nie mam wystarczającej reputacji, aby głosować teraz!)

Rozwiązanie zombat wykorzystuje zbyt wiele wywołań funkcji, ja nawet upraszczam kody. Używam PHP 5.4 do uruchomienia obu rozwiązań 100 000 razy, a oto wynik:

$str = 'Hello abc, have a nice day abc! abc!';
$pos = strpos($str, 'abc');
$str = substr_replace($str, '123', $pos, 3);

==> 1,85 sek

$str = 'Hello abc, have a nice day abc! abc!';
$str = preg_replace('/abc/', '123', $str, 1);

==> 1,35 sek

Jak widzisz. Wydajność preg_replace nie jest tak zła, jak wielu myśli. Proponuję więc klasyczne rozwiązanie, jeśli twój regularny ekspres nie jest skomplikowany.


Twój pierwszy fragment jest niesprawiedliwym porównaniem, ponieważ nie używa poprawnej implementacji. Nie sprawdzamy $posza false, więc gdy igła nie istnieje w stogu siana, to uszkodzić wyjście.
mickmackusa,

Dzięki @mickmackusa, masz rację. Ale nie o to chodzi. Powiedziałem, że ten kod jest uproszczony tylko w celu porównania wydajności implementacji.
Hunter Wu

Właśnie o to mi chodzi. Nigdy nie wolno dokonywać porównań porównawczych, które nie wykonują dokładnie tego samego procesu. Porównywanie jabłek do pół pomarańczy nie jest przydatne. Pełne wdrożenie pełnego podejścia innego niż wyrażenia regularne sprawi, że różnica prędkości będzie głębsza.
mickmackusa

Cóż, jeszcze raz dziękuję. Ale chcę znaleźć lepsze wdrożenie, a nie robić głębszych różnic.
Hunter Wu

2

Aby rozwinąć odpowiedź zombata (którą uważam za najlepszą), stworzyłem rekurencyjną wersję jego funkcji, która przyjmuje $limitparametr określający, ile wystąpień chcesz zastąpić.

function str_replace_limit($haystack, $needle, $replace, $limit, $start_pos = 0) {
    if ($limit <= 0) {
        return $haystack;
    } else {
        $pos = strpos($haystack,$needle,$start_pos);
        if ($pos !== false) {
            $newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
            return str_replace_limit($newstring, $needle, $replace, $limit-1, $pos+strlen($replace));
        } else {
            return $haystack;
        }
    }
}

Uwaga, nie ma jakości sprawdzenie $start_pos, więc jeśli to jest poza długością łańcucha, funkcja ta będzie generować: Warning: strpos(): Offset not contained in string.... Ta funkcja nie zastępuje, gdy $start_posjest poza długością. Dowód niepowodzenia: 3v4l.org/qGuVIR ... Twoja funkcja może łączyć return $haystackwarunki i unikać deklarowania zmiennych jednorazowych, takich jak: 3v4l.org/Kdmqp Jednak, jak powiedziałem w komentarzach w innym miejscu na tej stronie, wolałbym użyj bardzo czystego, bezpośredniego, nierekurencyjnego preg_replace()połączenia.
mickmackusa

tak, aby można było dodać tę elselinijkę$start_pos > strlen($haystack) ? $start_pos = strlen($haystack) : '';
Manojkiran.A

2

Dla sznurka

$string = 'OOO.OOO.OOO.S';
$search = 'OOO';
$replace = 'B';

//replace ONLY FIRST occurance of "OOO" with "B"
    $string = substr_replace($string,$replace,0,strlen($search));
    //$string => B.OOO.OOO.S

//replace ONLY LAST occurance of "OOOO" with "B"
    $string = substr_replace($string,$replace,strrpos($string,$search),strlen($search)) 
    //$string => OOO.OOO.B.S

    //replace ONLY LAST occurance of "OOOO" with "B"
    $string = strrev(implode(strrev($replace),explode(strrev($search),strrev($string),2)))
    //$string => OOO.OOO.B.S

Dla jednego znaku

$string[strpos($string,$search)] = $replace;


//EXAMPLE

$string = 'O.O.O.O.S';
$search = 'O';
$replace = 'B';

//replace ONLY FIRST occurance of "O" with "B" 
    $string[strpos($string,$search)] = $replace;  
    //$string => B.O.O.O.S

//replace ONLY LAST occurance of "O" with "B" 
    $string[strrpos($string,$search)] = $replace; 
    // $string => B.O.O.B.S

Pierwszy fragment substr_replace () kończy się niepowodzeniem, gdy szukany ciąg nie znajduje się w przesunięciu 0 ciągu wejściowego. Dowód niepowodzenia: 3v4l.org/oIbRv I obie substr_replace()techniki uszkadzają łańcuch wejściowy, gdy szukana wartość nie jest obecna. Dowód niepowodzenia: 3v4l.org/HmEml (I ta ostatnia technika ze wszystkimi revwywołaniami jest poważnie zawiła / trudna dla oczu.)
mickmackusa

2

Uzupełniając to, co mówili ludzie, pamiętaj, że cały ciąg jest tablicą:

$string = "Lorem ipsum lá lá lá";

$string[0] = "B";

echo $string;

„Borem ipsum lá lá lá”


3
Chyba że zawiera znaki wielobajtowe ... a wtedy twoja technika zawiedzie. Jak niefortunnie, że zaoferowałeś przykładowy ciąg wejściowy zawierający á. Demonstracja niepowodzenia
mickmackusa,

Możesz sprawdzić, czy masz stringciąg wielobajtowy, używającmb_strlen($subject) != strlen($subject)
RousseauAlexandre

Ten post nie próbuje odpowiedzieć na zadane pytanie.
mickmackusa,

2
$str = "/property/details&id=202&test=123#tab-6p";
$position = strpos($str,"&");
echo substr_replace($str,"?",$position,1);

Za pomocą substr_replace możemy zastąpić wystąpienie pierwszego znaku tylko ciągiem. ponieważ & jest powtarzane wiele razy, ale tylko na pierwszej pozycji musimy go zastąpić?


1

Ta funkcja jest silnie zainspirowana odpowiedzią @renocor. To sprawia, że ​​funkcja jest wielobajtowa bezpieczna.

function str_replace_limit($search, $replace, $string, $limit)
{
    $i = 0;
    $searchLength = mb_strlen($search);

    while(($pos = mb_strpos($string, $search)) !== false && $i < $limit)
    {
        $string = mb_substr_replace($string, $replace, $pos, $searchLength);
        $i += 1;
    }

    return $string;
}

function mb_substr_replace($string, $replacement, $start, $length = null, $encoding = null)
{
    $string = (array)$string;
    $encoding = is_null($encoding) ? mb_internal_encoding() : $encoding;
    $length = is_null($length) ? mb_strlen($string) - $start : $length;

    $string = array_map(function($str) use ($replacement, $start, $length, $encoding){

        $begin = mb_substr($str, 0, $start, $encoding);
        $end = mb_substr($str, ($start + $length), mb_strlen($str), $encoding);

        return $begin . $replacement . $end;

    }, $string);

    return ( count($string) === 1 ) ? $string[0] : $string;
}

0

Możesz użyć tego:

function str_replace_once($str_pattern, $str_replacement, $string){ 

        if (strpos($string, $str_pattern) !== false){ 
            $occurrence = strpos($string, $str_pattern); 
            return substr_replace($string, $str_replacement, strpos($string, $str_pattern), strlen($str_pattern)); 
        } 

        return $string; 
    } 

Znaleziono ten przykład z php.net

Stosowanie:

$string = "Thiz iz an examplz";
var_dump(str_replace_once('z','Z', $string)); 

Wynik:

ThiZ iz an examplz

Może to nieco obniżyć wydajność, ale jest to najłatwiejsze rozwiązanie.


Jeśli to jest wynik, o co chodzi? Czy nie powinno zastępować tylko pierwszej małej litery „Z” wielką literą „Z”? Zamiast zastąpić je wszystkie? Myślałem, że o to tutaj rozmawiamy ...
Swivel

Mój zło, to zastąpi tylko pierwsze wystąpienie. Edytowane.
happyhardik

Ta sama rada była już oferowana przez Bas prawie 3 lata wcześniej (i bez nadmiernego dzwonienia strpos()). Odebrane, ponieważ nie dodaje żadnej nowej wartości do strony.
mickmackusa,

0

Jeśli ciąg nie zawiera żadnych znaków wielobajtowych i jeśli chcesz zastąpić tylko jeden znak, możesz po prostu użyć strpos

Tutaj funkcja obsługująca błędy

/**
 * Replace the first occurence of given string
 *
 * @param  string $search  a char to search in `$subject`
 * @param  string $replace a char to replace in `$subject`
 * @param  string $subject
 * @return string
 *
 * @throws InvalidArgumentException if `$search` or `$replace` are invalid or if `$subject` is a multibytes string
 */
function str_replace_first(string $search , string $replace , string $subject) : string {
    // check params
    if(strlen($replace) != 1 || strlen($search) != 1) {
        throw new InvalidArgumentException('$search & $replace must be char');
    }elseif(mb_strlen($subject) != strlen($subject)){
        throw new InvalidArgumentException('$subject is an multibytes string');
    }
    // search 
    $pos = strpos($subject, $search);
    if($pos === false) {
        // not found
        return $subject;
    }

    // replace
    $subject[$replace] = $subject;

    return $subject;
}

0

Rozwiązanie Loop

<?php
echo replaceFirstMatchedChar("&", "?", "/property/details&id=202&test=123#tab-6");

function replaceFirstMatchedChar($searchChar, $replaceChar, $str)
{
    for ($i = 0; $i < strlen($str); $i++) {

        if ($str[$i] == $searchChar) {
            $str[$i] = $replaceChar;
            break;
        }
    }
    return $str;
}

-1

Oto prosta klasa, którą stworzyłem, aby zawinąć nieco zmodyfikowaną str_replace () funkcje .

Nasza funkcja php :: str_rreplace () pozwala również na wykonanie odwróconego, ograniczonego str_replace (), co może być bardzo przydatne, gdy próbujesz zastąpić tylko ostatnie X wystąpienie ciągu.

W obu tych przykładach użyto preg_replace () .

<?php
class php {

    /**
    * str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
    *
    * @param string   $find
    * @param string   $replace
    * @param string   $subject
    * @param int      $replacement_limit | -1 to replace all references
    *
    * @return string
    */
    public static function str_replace($find, $replace, $subject, $replacement_limit = -1) {
        $find_pattern = str_replace('/', '\/', $find);
        return preg_replace('/' . $find_pattern . '/', $replace, $subject, $replacement_limit);
    }

    /**
    * str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
    *
    * @param string   $find
    * @param string   $replace
    * @param string   $subject
    * @param int      $replacement_limit | -1 to replace all references
    *
    * @return string
    */
    public static function str_rreplace($find, $replace, $subject, $replacement_limit = -1) {
        return strrev( self::str_replace(strrev($find), strrev($replace), strrev($subject), $replacement_limit) );
    }
}

Twój post nie dodaje wartości temu już nasyconej stronie. Twoje rozwiązanie wyrażenia regularnego kończy się niepowodzeniem w wielu przypadkowych przypadkach, ponieważ użyłeś niepoprawnego narzędzia do zmiany znaków w ciągu igły. Dowód niepowodzenia: 3v4l.org/dTdYK Mocno pozytywnie oceniona i zaakceptowana odpowiedź z 2009 roku pokazuje już prawidłowe wykonanie tej techniki. Druga metoda nie odpowiada na zadane pytanie i została już dostarczona przez oLinkWebDevelopment.
mickmackusa,

-1
$str = "Hello there folks!"
$str_ex = explode("there, $str, 2);   //explodes $string just twice
                                      //outputs: array ("Hello ", " folks")
$str_final = implode("", $str_ex);    // glues above array together
                                      // outputs: str("Hello  folks")

Jest jeszcze jedna dodatkowa przestrzeń, ale nie miało to znaczenia, jak w moim przypadku dla skryptu backgound.


Ta technika została dostarczona przez toomuchphp w 2009 roku ! Oddałem głos, ponieważ twój post nie wnosi żadnej nowej wartości do naukowców. Przed opublikowaniem odpowiedzi upewnij się, że Twoje rozwiązanie jest unikalne dla strony i stanowi wartość dodaną dla strony.
mickmackusa

-3

to jest moja pierwsza odpowiedź tutaj, mam nadzieję, że zrobię to poprawnie. Dlaczego nie użyć czwartego argumentu funkcji str_replace dla tego problemu?

mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )

count: Jeśli przejdzie, zostanie ustawiona liczba wykonanych zamian.


edytuj: Ta odpowiedź jest niepoprawna, ponieważ czwarty parametr str_replace jest zmienną, której przypisuje się liczbę wykonanych zamian. Jest to niespójne z preg_replace , który ma czwarty parametr $limiti piąty parametr &$count.


Czwarte argumenty zostaną ustawione przez str_replace () na liczbę dokonanych zamian. Dlatego pojawia się błąd, gdy podajesz liczbę całkowitą, a nie zmienną.
arminrosu

-6

Łatwo jest znaleźć rozwiązanie, które zastąpi tylko pierwszą lub pierwszą parę instancji (podając wartość zliczania). Nie ma wielu rozwiązań, które mogłyby zastąpić ostatnią lub ostatnią parę instancji.

Może coś takiego jak str_replace ($ find, $ replace, $ subject, -3) powinno zastąpić ostatnie trzy instancje.

W każdym razie tylko sugestia.


4
Po co odpowiadać na pytanie z sugestią, kiedy odpowiedź została zaakceptowana dwa lata wcześniej ?!
mbinette

Również -3 nie będzie działać jako parametr, ponieważ czwarty parametr jest wyjściowy, a nie wejściowy. Byłoby lepiej, gdybyś przetestował to, co proponujesz, zamiast publikować kod, który ulega awarii.
Ghostwriter78

Tak, to źle, jednak dlaczego mam awarię pustego ekranu, kiedy próbuję? Zrobiłem zwykłe raportowanie błędów (E_ALL); ini_set („display_errors”, 1); Nadal występuje błąd pustego ekranu.
Doug Cassidy,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.