Czasem zdarza się tak, że musimy podzielić nasz ciąg tekstowy w wyznaczonych miejscach na kilka mniejszych. W tej sytuacji funkcja
explode() sprawdza się wręcz znakomicie. Problem jednak pojawia się gdy chcemy dzielić za pomocą więcej niż jednego separatora, gdyż samo explode() obsługuje jedynie separator w postaci pojedynczego "stringu". Na poniższych przykładach pokazałem różne sposoby rozbicia ciągu przy użyciu trzech separatorów:
& ? /.
Rozwiązania
Najtrywialniejszym sposobem na poprawne rozbicie wg kilku separatorów byłoby wywołanie ponownie
explode() dla każdego elementu wynikającego z pierwszego użycia tej funkcji, jednak ten sposób aż bije po oczach swym prostactwem więc nawet nie będę bawił się w jego poprawną implementację, a użyję jedynie takiego (dużego i nierealizującego zadania) przybliżenia:
for ($i = 0; $i < 100000; $i++)
{
$temp = explode ( "/" , $string);
foreach($temp as $elem)
{
$e = explode("?", $elem);
$e = explode("&", $elem);
}
}
Kolejnym pomysłem jest iteracyjne podejście - używanie na przemian
explode() i
implode() w celu stworzenia pojedynczej tablicy z wynikiem.
for ($i = 0; $i < 100000; $i++)
{
$temp = explode ( "/" , $string);
$temp = explode("&", implode("&", $temp));
$temp = explode("?", implode("?", $temp));
}
Skoro już postanowiliśmy iść w stronę podmian to warto by również rozważyć wielokrotne użycie
str_replace().
for ($i = 0; $i < 100000; $i++)
{
$temp = str_replace("&", "/", $string);
$temp = str_replace("?", "/", $temp);
$temp = explode ( "/" , $temp);
}
Nim przejdziemy do wyrażeń regularnych przytoczę tu jeszcze jeden przykład, który niestety działa jedynie w przypadku separatorów będących pojedynczymi znakami. Co ciekawe mimo, iż wykorzystanie funkcji
strtok() wydaje się być potwornie nieefektywne to wyniki zbliżone są do naprzemiennego wywoływania funkcji
explode() i
implode().
for ($i = 0; $i < 100000; $i++)
{
$tok = strtok($string, "/&?");
$temp = array();
while ($tok !== false)
{
$temp[] = $tok;
$tok = strtok("/&?");
}
unset($tok);
strtok('', '');
}
Ale czy mamy ograniczać się jedynie do takich kombinowanych sposobów? Może jest jedna "magiczna" funkcja, która zrobi to o czym myślimy? Ano jest i wykorzystuje magię wyrażeń regularnych, a zwie się ona
preg_split().
for ($i = 0; $i < 100000; $i++)
{
$temp = preg_split("/[\/&?]/",$string);
}
Tutaj jednak warto zwrócić uwagę na fakt, że jeśli naszym separatorem jest
znak wielobajtowy to funkcja ta będzie generować dodatkowe puste elementy w wynikowej tablicy (jeden dodatkowy element na każde wystąpienie separatora). Sytuacja ta nie ma jednak miejsca jeśli separatorem nie jest znak a ciąg znaków, nawet jeśli wszystkie zawarte w nim elementy są znakami wielobajtowymi.
Tak czy inaczej remedium na ten problem jest funkcja
mb_split():
for ($i = 0; $i < 100000; $i++)
{
$temp = mb_split("[\/&?]",$string);
}
Ostatnim przytoczonym tu przykładem będzie połączenie wyrażeń regularnych wykorzystanych w funkcji
preg_replace() wraz z pojedynczym wywołaniem
explode():
for ($i = 0; $i < 100000; $i++)
{
$temp = preg_replace("/[&?]/","/",$string);
$temp = explode("/", $temp);
}
Testy wydajności
Jak widać istnieje wiele możliwości wykonania zadania dzielenia ciągu z użyciem wielu równorzędnych separatorów, ale które rozwiązanie jest najefektywniejsze? Popatrzmy na wyniki:
single explode: 0.1573359966
multiple explode: 1.0633759499
explode & implode: 0.7556319237
replace & explode: 0.4618999958
strtok: 0.6737470627
preg_split: 0.9344220161
mb_split: 1.1594960690
preg_replace & explode: 1.1028780937
Dla separatorów składających się również z kilku znaków otrzymujemy takie wyniki:
single explode: 0.1560928822
multiple explode: 1.3294098377
explode & implode: 1.1651928425
replace & explode: 1.0913951397
preg_split: 1.8065340519
mb_split: 4.9289889336
preg_replace & explode: 1.8077979088
Jak widać wszystkie czasy wzrosły, przy czym wzrost
mb_split() wynika w dużej mierze z używania wielobajtowych znaków.
Jeśli jednak zostaniemy przy tych trzech jedno-znakowych separatorach, a jedynie zwiększymy rozmiar badanego ciągu otrzymamy takie oto wyniki (
10x mniej iteracji pętli testujących):
single explode: 0.2046608925
multiple explode: 1.8381199837
explode & implode: 1.0454828739
replace & explode: 0.5224039555
strtok: 1.0597200394
preg_split: 1.8259940147
mb_split: 2.1428530216
preg_replace & explode: 1.8069810867
Jeśli dodatkowo zamiast trzech separatorów użyjemy 5 będziemy mieć takie oto wyniki:
single explode: 0.2125821114
multiple explode: 4.1121268272
explode & implode: 3.3804459572
replace & explode: 1.6378400326
strtok: 3.4061551094
preg_split: 2.7953231335
mb_split: 6.4811940193
preg_replace & explode: 3.0788750648
Wnioski
Jak widać wydajność metod różni się w zależności tak od długości badanych ciągów, jak i ilości oraz postaci separatorów. Porównując wyniki możemy jednak dojść do wniosku, że najlepszym rozwiązaniem jest użycie duetu
str_replace() wraz z
explode(), który to we wszystkich testach okazał się najszybszym rozwiązaniem. Pozostałe metody są w zasadzie porównywalne, choć stosunek ich wydajności różni się w zależności od parametrów zadania (
długość ciągu i postać separatorów). Pomimo to możemy jasno stwierdzić, że wielokrotne używanie
explode() (
multiple explode) jest całkowicie nieefektywne w porównaniu do pozostałych metod (zwłaszcza, że nie zaimplementowano tej funkcji w całości). Podobnie sytuacja wygląda w przypadku
mb_split(), którą używać należałoby jedynie w przypadku skomplikowanych podziałów, a nie prostego dzielenia wg podanych słów.
Wykorzystany kod do testowania można pobrać z mojego chomika (
link).