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.
Diskuse
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";
}
?>
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.
Jakub Vrána :
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.
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).
Jakub Vrána :
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).
Jakub Vrána :
V PHP nejsou řetězce uloženy jako spojový seznam, ale jako posloupnost bajtů na jednom místě paměti.
Jakub Vrána :
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.