Záměna řetězce s alternativní akcí

Školení, která pořádám

Někdy potřebujeme v textu nahradit řetězec a když se to nepovede, tak provést záložní operaci (třeba informovat o tom, že se to nepovedlo). To se dá udělat několika způsoby:

<?php
if (preg_match($pattern, $s)) {
    $s = preg_replace($pattern, $replace, $s);
} else {
    echo "Řetězec nenalezen.\n";
}

$s2 = preg_replace($pattern, $replace, $s);
if ($s2 !== $s) {
    $s = $s2;
} else {
    echo "Řetězec nenalezen.\n";
}
?>

Oba způsoby jsou poměrně krkolomné a pomalé. Elegantní řešení přináší až PHP 5.1.0, které funci preg_replace přidává parametr count:

<?php
$s = preg_replace($pattern, $replace, $s, -1, $count);
if (!$count) {
    echo "Řetězec nenalezen.\n";
}
?>

Čtvrtý parametr (v kódu nastavený na -1) je limit, který se dá použít pro omezení počtu provedených změn a v PHP je už od verze 4.0.1.

Jakub Vrána, Dobře míněné rady, 4.7.2007, diskuse: 11 (nové: 0)

Diskuse

Roman Pištěk:

Není podmínka v první příkladu "otočena"?

<?php

$s2
= preg_replace($pattern, $replace, $s);
if (
$s2 !== $s) {
    $s = $s2;
} else {
    echo "Řetězec nenalezen.\n";
}

?>

ikona Jakub Vrána OpenID:

Díky za upozornění, opravil jsem to.

ikona RAY:

Upřímně řečeno, příliš nerozumím tomu, proč by parametr $count měl být výhodnější. I bez něj můžeme sestrojit velice jednoduchou podmínku:

<?php

if ($s !== $s2 = preg_replace($pattern, $replace, $s)) {
    echo 'Nahrazeno, výsledek: '.$s2;
} else {
    echo 'Bez změny/Nenalezeno';
}

?>

Leda, že by využití $count bylo procesorově výhodnější, ale vzhledem k operátoru !== není třeba ani přetypovávat...
Zajímavé by bylo zjistit, jestli více zpomalující dokonce nebude využití parametru $count.

ikona Jakub Vrána OpenID:

V článku jsem to asi dostatečně nezdůraznil, z kódu <?php if ($s2 !== $s) { $s = $s2; } ?> je nicméně jasné, že chci výsledný řetězec dát zpátky do $s.

ikona RAY:

Takže tam přidáme jeden řádek:

<?php

if ($s !== $s2 = preg_replace($pattern, $replace, $s)) {
    $s = $s2;
    echo 'Nahrazeno, výsledek: '.$s;
} else {
    echo 'Bez změny/Nenalezeno';
}

?>

A stále mi to nepřipadá méně elegantní - je to jen jeden přehledný řádek navíc. (Co získáme přepsáním původního obsahu $s?).

Ale pokud Vám šlo o napsání co nejkratšího kódu, tak není třeba na můj příspěvek dál reagovat (myslel jsem si, že je v tom ukryto třeba něco hlubšího).

ikona Jakub Vrána OpenID:

Přepsat původní $s je potřeba, když se s tím potom dále pracuje (třeba se provádí další záměny).

Mnou prezentovaný kód je nejen nejkratší, ale i nejrychlejší a subjektivně nejpřehlednější. Proto jsem s ním chtěl seznámit čtenáře. Výraz ($s !== $s2 = ...) mi připadá nečitelný, i kdyby byl třeba kratší (což není).

kozotoč:

Nechci se nějak přít, ale.. je možné to nějak dokázat (že verze s $count je rychlejší)? Co když ta režie dalšího parametru uvnitř funkce (rezervování paměťového místa pro něj a inicializace jeho hodnoty, zapisování do něj, ...) je přeci jen o pár nanosekund pomalejší než RAYova verze?

Sahrk:

A já bych řekl že porovnání dvou integerů je i s jejich vytvářením mnohem rychlejší než porovnání dvou řetězců, které se porovnávají bajt po bajtu a jelikož bývají ukládány jako spojový seznam, tak na přečtení každého dalšího písmene je potřeba přečíst o několik bajtů více (adresu dalšího prvku).

ikona Jakub Vrána OpenID:

V PHP nejsou řetězce uloženy jako spojový seznam, ale jako posloupnost bajtů na jednom místě paměti.

ikona Jakub Vrána OpenID:

Dá se to dokázat benchmarkem, který jsem si provedl. Byl pouze "quick & dirty", proto jsem nepublikoval výsledky, ale varianta s $count byla trochu rychlejší.

RATMex B:

To je skoro až filozofická otázka. Všeobecná odpoveď neexistuje a pre konkrétnu situáciu nemusí závisieť len od samotných parametrov, ale dokonca aj kompilácie a samotnej architektúry procesora.

Keď sa pozeráme iba na rozdielny priebeh kódu medzi RAY-ovou a "count" verziou, pričom zanedbáme z nich rôzne režijné a jednorázové drobnosti až na bottlenecky, budú vyzerať približne takto:

RAY-ova verzia:
memcmp(string1, string2)
v is_identical_function(), kde parametre sú pôvodný a výsledný string.

"count" verzia:
++*replace_count;
v php_pcre_replace_impl(), pre každý výskyt patternu, kde replace_count je pointer na int.

Dôsledky sú tým pádom veľmi prosté. Kým takýto vstup:
<?php
$s
= str_repeat("a", 1e6);
$pattern = "/a/";
$replace = "b";
?>
zbehne rýchlejšie u RAY-ovej verzie (memcmp skončí okamžite, ale counter sa pripočítava miliónkrát), tak na druhej strane vstup
<?php
$s
= str_repeat("a", 1e6) . "b";
$pattern = "/b/";
$replace = "a";
?>
bude u "count" verzie efektívnejší (memcmp porovná milión bajtov až kým nájde rozdiel, ale counter sa pripočíta iba raz).

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.