Záměna řetězce s alternativní akcí
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
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";
}
?>
Jakub Vrána
:
Díky za upozornění, opravil jsem to.


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.

