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).

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2022 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.