“The best way to get the right answer on the Internet is not to ask a question, it's to post the wrong answer.”
QA Engineer walks into a bar. Orders a beer. Orders 0 beers. Orders 999999999 beers. Orders a lizard. Orders -1 beers.
preg_match("/((.*?)\s){10}/", $strn, $matches);
$eersteTienWoorden = $matches[0];
de vraag is alleen of explode() niet sneller werkt
//edit
ook een benchmark:
http://download.bleq.nl/bench_php.phps
[ Voor 45% gewijzigd door Fl4sh3r op 04-05-2005 12:39 ]
Verwijderd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?php $afterSpace = 10; $offset = 0; $string = "Reageer ontopic, plaats geen onzinnige berichten en ga niet flamen of uitlokken (trollen)."; while ($offset !== false && $i++ < $afterSpace) $offset = strpos($string, " ", $offset + 1); if ($offset === false) echo "not found"; else echo substr($string, 0, $offset); ?> |
[ Voor 18% gewijzigd door MBV op 04-05-2005 11:51 ]
Verwijderd
Het ligt er natuurlijk aan hoe groot de gemiddelde $afterSpace is die je gebruikt, maar ik weet bijna zeker dat mijn manier sneller is dan het gebruik van een explode (indien 500 woorden: array met 500 items vullen). Benchmarks laat ik graag aan de TS overMBV schreef op woensdag 04 mei 2005 @ 11:50:
dat gaat echt langer duren, Emiel. Combinatie van explode en implode lijkt mij wel sneller dan dat for-loopje van mij. Maar de basis blijft echt hetzelfde. Maar doe eens een benchmark: hoelang doet hij erover als je 1000x de eerste 500 woorden wilt vinden met explode en implode? Wordt dat echt onacceptabel? In het ergste geval zou je een CGI-script zelf kunnen schrijven in C++, dat is veel sneller.
Goed, ik verveelde me:
Zie http://public.emielmols.info/benchmark.phps
Resultaat:
1
2
3
| Icarus ~ # php benchmark.php Emiel: 0.017609 MBV: 0.144588 |
Conclusie: explode is traaaag (zelfs bij ~15 woorden), zoals verwacht. Ik verwacht niet dat implode veel sneller is.
[ Voor 17% gewijzigd door Verwijderd op 04-05-2005 12:08 . Reden: +benchmark ]
“The best way to get the right answer on the Internet is not to ask a question, it's to post the wrong answer.”
QA Engineer walks into a bar. Orders a beer. Orders 0 beers. Orders 999999999 beers. Orders a lizard. Orders -1 beers.
Die benchmark klopt niet, want die van Emiel wordt maar 1 keer gedaan en geen 1000Verwijderd schreef op woensdag 04 mei 2005 @ 11:56:
Goed, ik verveelde me:
Zie http://public.emielmols.info/benchmark.phps
Resultaat:
code:
1 2 3 Icarus ~ # php benchmark.php Emiel: 0.017609 MBV: 0.144588
Conclusie: explode is traaaag (zelfs bij ~15 woorden), zoals verwacht. Ik verwacht niet dat implode veel sneller is.
Kijk eens naar die "&& $i++ < $afterSpace", die $i wordt nooit meer op 0 gezet. De eerste keer $i = 0, de tweede en keren daarna is ie al >= $afterSpace en zal die hele loop dus niet worden uitgevoerd.
http://download.bleq.nl/bench_php2.phps de code is niet al te netjes, maar t doet z'n ding
1
2
3
4
5
6
7
| Tot spatie Fl4sh3r (preg_match) Emiel (strpos substr) MBV (explode) 10 0.46522402763367 0.88210487365723 1.1723880767822 20 0.52316689491272 1.1414139270782 1.3555870056152 30 0.57671904563904 1.4923930168152 1.4994080066681 40 0.63903498649597 1.6435859203339 1.65944480896 50 0.68750810623169 1.9021379947662 1.8228280544281 60 0.74108505249023 2.1515579223633 1.9610879421234 |
[ Voor 54% gewijzigd door Fl4sh3r op 04-05-2005 12:55 ]
Die gaat gewoon netjes met StrPos de locatie van de spaties opzoeken en op dat punt de string splitsen. Hiervoor moet hij ook weer een array alloceren en daar de strings in doen. Dan kan je natuurlijk het makkelijkst gewoon door de string wandelen met StrPos. Dat zal de explode functie tenslotte ook moeten doen.
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Verwijderd
Goed punt inderdaadFl4sh3r schreef op woensdag 04 mei 2005 @ 12:42:
[...]
Die benchmark klopt niet, want die van Emiel wordt maar 1 keer gedaan en geen 1000
Kijk eens naar die "&& $i++ < $afterSpace", die $i wordt nooit meer op 0 gezet. De eerste keer $i = 0, de tweede en keren daarna is ie al >= $afterSpace en zal die hele loop dus niet worden uitgevoerd.
http://download.bleq.nl/bench_php2.phps de code is niet al te netjes, maar t doet z'n ding
code:
1 2 3 4 5 6 7 Tot spatie Fl4sh3r (preg_match) Emiel (strpos substr) MBV (explode) 10 0.46522402763367 0.88210487365723 1.1723880767822 20 0.52316689491272 1.1414139270782 1.3555870056152 30 0.57671904563904 1.4923930168152 1.4994080066681 40 0.63903498649597 1.6435859203339 1.65944480896 50 0.68750810623169 1.9021379947662 1.8228280544281 60 0.74108505249023 2.1515579223633 1.9610879421234
Wist je dat eval() een hele trage functie is en dat die waarschijnlijk de benchmarkresultaten kan beinvloeden?Fl4sh3r schreef op woensdag 04 mei 2005 @ 12:42:
[...]
http://download.bleq.nl/bench_php2.phps de code is niet al te netjes, maar t doet z'n ding
1
2
3
4
5
6
7
8
9
10
11
12
| function pruts($string, $max) { $counter=0; for ($i=0;$i<strlen($string);$i++) { if ($string{$i} == " ") $counter++; if ($counter==$max) break; } return substr($string,0,$i); } |
Ik zal ook eens kijken of ik deze kan benchen
Gebenched (source)
1
2
3
4
5
| ~/www/john$ php -q ./spatiesbench.php Emiel: 0.0044629 MBV: 0.0360346 GX: 0.0092414 Fl4sh3r: 0.0178197 |
winnaar is duidelijk
[ Voor 48% gewijzigd door GX op 04-05-2005 19:39 ]
Zelfde probleem als ik al eerder noemde over die van Emiel, je moet die $i op 0 zetten.GX schreef op woensdag 04 mei 2005 @ 19:21:
PHP:
1 2 3 4 5 6 7 8 9 10 11 12 function pruts($string, $max) { $counter=0; for ($i=0;$i<strlen($string);$i++) { if ($string{$i} == " ") $counter++; if ($counter==$max) break; } return substr($string,0,$i); }
Ik zal ook eens kijken of ik deze kan benchen
Gebenched (source)
code:
1 2 3 4 5 ~/www/john$ php -q ./spatiesbench.php Emiel: 0.0044629 MBV: 0.0360346 GX: 0.0092414 Fl4sh3r: 0.0178197
winnaar is duidelijk
http://download.bleq.nl/bench_php3.phps
1
2
3
4
5
6
7
8
9
| strlen($string): 823 spaties Fl4sh3r Emiel MBV GX 10 0.058330059051514 0.021034002304077 0.60167694091797 1.3237280845642 20 0.065638065338135 0.022664070129395 0.76059293746948 2.6887600421906 30 0.062294006347656 0.020734071731567 0.93934679031372 4.1746971607208 40 0.067097902297974 0.022790908813477 1.1035730838776 5.7865397930145 50 0.080018043518066 0.021029949188232 1.3348860740662 7.0966868400574 60 0.061047792434692 0.020951986312866 1.4608480930328 ?? |
EDIT:
de tijden van Emiel klopten niet, zoals T-Mob hieronder constanteert, $offset werd niet op 0 gezet.
1
2
3
4
5
6
7
8
9
| strlen($string): 823 spaties Fl4sh3r Emiel MBV GX 10 0.05925989151001 0.28145623207092 0.59266304969788 1.3018169403076 20 0.062365055084229 0.53805017471313 0.76555705070496 2.6246871948242 30 0.06270694732666 0.78621006011963 0.9334499835968 4.4311029911041 40 0.063833951950073 1.0366020202637 1.0999629497528 5.4068660736084 50 0.062085866928101 1.2809829711914 1.2690479755402 6.9562339782715 60 0.063846111297607 1.6831789016724 ?? ?? |
/EDIT
Volgens mij werkt break niet op een for... die tijden zijn wel erg extreem bij GX
De eval had blijkbaar toch invloed. Mijn stukje was natuurlijk korter, dus minder tijd nodig bij eval. Uiteindelijk blijkbaar niet sneller dan Emiel's manier
Ik ga toch voor de reguliere expressie
Wat er met mijn tijden niet klopt, geen idee

[ Voor 51% gewijzigd door Fl4sh3r op 04-05-2005 21:29 ]
Nou.. niet echt. Had er vanmiddag ook al even mee gespeeld (ook jouw methode toegevoegd). Mijn conclusie was dat het er nogal aan ligt hoe lang de string is waarin je zoekt en hoeveel woorden je wil hebben. Sommige methoden zijn met lange strings erg duur (explode vooral), andere worden erg duur als je veel woorden zoekt (strpos-methode en string doorwandelen worden harder duurder met het aantal gezochte woorden dan explode()-methode). De enige methode die bij elke combinatie acceptabel presteerde is die met de regex.
In jouw functie kan trouwens nog een optimalisatie: de strlen() kan je uit het for-loopje halen door hem slechts één maal te berekenen voor je het loopje start. Scheelt net weer ff
edit:
Volgens mij gaat daar iets mis. Emiels tijden zijn namelijk nagenoeg constant en dat is onmogelijk aangezien er per stap 10 strpos()-en meer uitgevoerd moeten worden... Dat geldt trouwens ook voor je eigen tijden...Fl4sh3r schreef op woensdag 04 mei 2005 @ 20:58:
De eval had blijkbaar toch invloed. Mijn stukje was natuurlijk korter, dus minder tijd nodig bij eval. Uiteindelijk blijkbaar niet sneller dan Emiel's manier
edit2: Fout bij Emiel gevonden... Je moet $offset binnen de 1000*loopen op nul zetten, niet ervoor. Nu berekent ie maar 1x de output per stap. (Haha, slow-chat editten)
[ Voor 33% gewijzigd door T-MOB op 04-05-2005 21:28 ]
Regeren is vooruitschuiven
Ik kwam er inderdaad ook achterT-MOB schreef op woensdag 04 mei 2005 @ 21:15:
[...]
Nou.. niet echt. Had er vanmiddag ook al even mee gespeeld (ook jouw methode toegevoegd). Mijn conclusie was dat het er nogal aan ligt hoe lang de string is waarin je zoekt en hoeveel woorden je wil hebben
Echter deed bij lange strings mijn functie er nog steeds niet meer dan 1 seconde over (geen één overigens, met de emiel-fout er nog in); ik geef eval de schuld
Overigens vond ik persoonlijk het duurder worden best jammer, had ik ook niet van te voren aan zien komen.
Klopt; echter vond ik het in dit geval niet bijster noodzakelijkIn jouw functie kan trouwens nog een optimalisatie: de strlen() kan je uit het for-loopje halen door hem slechts één maal te berekenen voor je het loopje start. Scheelt net weer ff
geh, bij mijn script krijg ik af en toe negatieve waarden bij average times
negatieve waarden gerepareerd, nu is dit mijn output bij 60 spaties en die lange string uit een andere source hier ergens:
1
2
3
4
| Emiel: 0.203141236305 MBV: 0.257047271729 GX: 0.0108384370804 Fl4sh3r: 0.0938363075256 |
Ziet er grappig uit natuurlijk, maar er lijkt geen drol van te kloppen.
alhier source ik raad aan om de get_microtime() sowieso even over te nemen
[ Voor 26% gewijzigd door GX op 04-05-2005 22:04 ]
strlen($string): 823 spaties Fl4sh3r Emiel MBV GX 10 0.0641419887543 0.145102977753 0.393733978271 0.744172096252 20 0.094183921814 0.268368005753 0.477469921112 1.466173172 30 0.127284049988 0.394625902176 0.560317993164 2.29862523079 40 0.156450986862 0.523796081543 0.64247584343 3.01198291779 50 0.19378900528 0.667150974274 0.728325128555 3.8662071228 60 0.22429895401 0.77206993103 0.814624071121 4.63668894768
EDIT:
GX schreef op woensdag 04 mei 2005 @ 21:41:
edit:
geh, bij mijn script krijg ik af en toe negatieve waarden bij average times
Leip... test je op Blue Gene ofzo?
[ Voor 17% gewijzigd door T-MOB op 04-05-2005 21:52 ]
Regeren is vooruitschuiven
1
2
3
4
5
6
7
| Emiel MBV GX Fl4sh3r 10 0.0362541437149 0.153687977791 0.142634916306 0.0200412273407 20 0.0782668352127 0.216257309914 0.271225726604 0.0249896883965 30 0.0866009632746 0.217268466949 0.328568951289 0.0313873449961 40 0.0980850279331 0.22532158494 0.391987425089 0.0371068477631 50 0.113593840599 0.235495605469 0.46418448925 0.0471341323853 60 0.12773001194 0.247109127045 0.535764431953 0.055618528525 |
[ Voor 210% gewijzigd door GX op 04-05-2005 22:31 ]
1
2
3
4
5
6
7
8
9
10
11
12
13
| $afterSpace = 10; $offset = -1; do { $offset = strpos($string, ' ', ++$offset); } while (--$afterSpace && $offset !== false); if ($offset === false) echo 'not found'; else echo substr($string, 0, $offset); |
Hier zitten dus de volgende optimalisaties in:
1) de $i is overbodig; je kan net zo goed $afterSpace naar 0 laten tellen (en een logische afvraging is sneller dan de vergelijking met een andere variabele)
2) een do-while scheelt hier 1 conditie-afvraging tov een while
3) pre-increments zijn sneller dan optellingen (en ook sneller dan post-increments btw)
4) single quotes gebruikt voor de needle in de strpos functie
5) in conditie-afvragingen kan volgorde belangrijk zijn. In deze test wordt --$afterSpace eerder 0 dan dat $offset false wordt. Dat scheelt je in deze benchmark dus 1 test in de conditie-afvraging
Overigens is Emiel's methode hier de beste omdat hij ook situaties opvangt waarbij het aantal spaties niet gevonden wordt, en daar netjes melding van geeft. Dat mis ik in de andere methoden. Eigenlijk zou je dus dit moeten gebruiken om een eerlijk vergelijk te krijgen:
1
2
3
4
5
6
7
8
9
10
| $afterSpace = 10; $offset = -1; do { $offset = strpos($string, ' ', ++$offset); } while (--$afterSpace); echo substr($string, 0, $offset); |
en dan blijkt deze versie opeens veel dichter bij de snelheid van de RegExp methode te komen
Als je zaken gaat benchen zorg dan niet alleen dat de functies die je gaat benchen niet alleen dezelfde input krijgen, maar ook dezelfde output geven. Gebruik ook gewoon functies, en roep die 1000x aan - de functiecall overhead is dan in alle gevallen gelijk, en je kan de output toetsen. Je weet dan ook dat alle functies hetzelfde doen, en dat je dus ook hetzelfde benched.
In dit geval zou ik gewoon het volgende doen:
1
2
3
4
5
6
7
| function benchme($string, $spaces) { //-- hier de methode //-- return part; gedeelte string t/m aantal spaties, of false return $something; } |
[ Voor 42% gewijzigd door crisp op 04-05-2005 23:16 ]
Intentionally left blank
Ho eens; wanneer bijvoorbeeld de mijne het juiste aantal spaties niet heeft gevonden is $i evengroot als de gehele string, en output de substr() dus netjes de hele string; Een foutmelding zou hier (naar mijn mening) niet gewenst danwel overbodig zijn.crisp schreef op woensdag 04 mei 2005 @ 22:50:
Overigens is Emiel's methode hier de beste omdat hij ook situaties opvangt waarbij het aantal spaties niet gevonden wordt, en daar netjes melding van geeft. Dat mis ik in de andere methoden. Eigenlijk zou je dus dit moeten gebruiken om een eerlijk vergelijk te krijgen:
in any case, meer benchmarks \o/
1
2
3
4
5
6
7
8
| gx@oceserver:~/www/john$ php -q ./spatiesbench.php Emiel MBV GX Fl4sh3r Crisp 10 0.036597 0.145239 0.143305 0.021869 0.024767 20 0.053444 0.162800 0.217552 0.029925 0.035671 30 0.069438 0.178288 0.293107 0.039500 0.046501 40 0.085331 0.192821 0.366168 0.049158 0.057144 50 0.101603 0.207580 0.443467 0.059285 0.067738 60 0.117449 0.220994 0.529521 0.069501 0.078168 |
Lekker crisp z'n edit niet gelezen. Oude manier gebenched.
@crisp; die strlen had ik in nieuwere benches inderdaad al in een variabele geknikkert
[ Voor 9% gewijzigd door GX op 04-05-2005 23:50 ]
Ik heb uiteraard wat dingen herschreven om te voldoen aan de output-rule. GX: jij bent bijvoorbeeld ook sneller af door de strlen op te slaan in een variabele ipv elke iteratie uit te vragen
[ Voor 14% gewijzigd door crisp op 04-05-2005 23:48 ]
Intentionally left blank
Ik heb de methode van GX ook even nog wat sneller gemaakt
Edit: Fl4sh3r methode gefixed, dus hij mag weer meedoen
[ Voor 53% gewijzigd door crisp op 05-05-2005 00:32 ]
Intentionally left blank
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| function Fl4sh3r($string, $spaces) { if(preg_match("/((.*?)\\s){".$spaces."}/", $string, $matches)==0) return false; return $matches[0]; } function Emiel($string, $spaces) { $offset = -1; do { $offset = strpos($string, ' ', ++$offset); if($offset === false) return false; } while(--$spaces); return substr($string, 0, $offset); } function GX($string, $spaces) { $strlen = strlen($string); for($i=0;$i<$strlen;$i++) { if($string{$i} == ' '&& (!--$spaces)) return substr($string,0,$i); } return false; } function MBV($string, $spaces) { $exploded = explode(' ', $string); if(count($exploded) <= $spaces) return false; $output = ''; for($i=0;$i<$spaces;$i++) $output .= $exploded[$i].' '; return $output; } |
//edit MBV erbij
[ Voor 23% gewijzigd door Fl4sh3r op 05-05-2005 00:36 ]
Ok, meegenomen in http://allcrispy.com/benchmarks.phps (voor zover daadwerkelijk sneller)Fl4sh3r schreef op donderdag 05 mei 2005 @ 00:30:
Ik was net de methodes aan het herschrijven zodat ze false teruggeven als het opgevraagde aantal spaties er niet in staat, bovendien wat optimalisatie:
Jouw methode heeft 1 groot manco: op het moment dat je wilt gaan zoeken naar meer spaties dan er in de string zitten gaat jouw regexp backtracken, wat (bij mij) parsetimes oplevert van +/- 1 seconde per functiecall(!)
MBV's methode kon ook anders mbv array_slice, dus daar heb ik ook een alternatief van gemaakt die bij grotere waarden beter lijkt te performen.
[ Voor 15% gewijzigd door crisp op 05-05-2005 00:46 ]
Intentionally left blank
Verwijderd
Oja, waar is de TS gebleven
[ Voor 16% gewijzigd door Verwijderd op 05-05-2005 00:47 ]
1
2
3
4
| function benchme_MBV2($string, $spaces) { return implode(' ', array_slice(explode(' ', $string), 0, $spaces)); } |
Intentionally left blank
Dat probleem van die backtracking,

Mijn laatste versie:
http://download.bleq.nl/bench_php4.phps
nu slapen

[ Voor 3% gewijzigd door Fl4sh3r op 05-05-2005 01:01 ]
Als je hem met false wilt hebben kan ook:Fl4sh3r schreef op donderdag 05 mei 2005 @ 01:01:
Die laatst rewrite van MBV geeft geen false terug, wat ik netter vind dan de hele string, maar goed MHO.
Dat probleem van die backtracking,niet getest, wel op te lossen, maar maakt de code groter en dus trager.
Mijn laatste versie:
http://download.bleq.nl/bench_php4.phps
nu slapen
1
2
3
4
5
6
| function benchme_MBV2($string, $spaces) { $exploded = explode(' ', $string); if (count($exploded) < $spaces) return false; return implode(' ', array_slice($exploded, 0, $spaces)); } |
maar of je false of juist de hele string terug wilt hebben hangt af van de context waarin je zoiets wilt gebruiken. count() is overigens niet zo heel duur in dit geval.
Overigens is bovenstaande functie bij grotere strings en meer spaties sneller dan de 'improved Emiel function', en op het moment dat je naar meer spaties vraagt dan er zijn is hij zelfs aanzienlijk sneller.
Daarbij zit dit logisch ook goed in elkaar, en zal dus IRL mijn voorkeur hebben boven de reguliere expressie. Het backtrack probleem zal namelijk zeker gaan voorkomen in echte situaties...
[ Voor 7% gewijzigd door crisp op 05-05-2005 01:13 ]
Intentionally left blank
die $exploded die je count() is nooit gemaakt... (te laat)
Als ik bij de mijne erbij zet:
if(count(explode(' ',$string)) <= $spaces) return false;
werkt ie wel, maar is ie behoorlijk traag... als je suggesties hebt, ik lees ze graag (morgen)
[ Voor 7% gewijzigd door Fl4sh3r op 05-05-2005 01:14 ]
suggestie:
1
2
3
4
| function benchme_Fl4sh3r($string, $spaces) { return preg_match('/^([^ ]* ){' . $spaces . '}/', $string, $matches) ? $matches[0] : false; } |
en ja, die is lightning-fast (zelfs sneller dan jouw regexp, maar deze heeft daarbij ook geen backtrack probleem omdat het een greedy match is)
Overigens was de \s bij jou ook foutief omdat die meer matched dan alleen een spatie.
[ Voor 52% gewijzigd door crisp op 05-05-2005 01:36 ]
Intentionally left blank
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
1
2
3
4
5
6
7
8
9
10
| function benchme($string, $spaces) { $output = strtok($string, ' '); while (--$spaces && $token = strtok(' ')) { $output .= ' ' . $token; } return $spaces ? false : $output; } |
Een snellere check om te kijken of het aantal spaties ueberhaupt wel voorkomt in de gegeven string kan eventueel mbv substr_count.
Een andere methode die wellicht al deze methoden qua snelheid kan overtreffen is wanneer je zelf een functie schrijft in bijvoorbeeld C(++) en dat compileert als een loadable module
[ Voor 19% gewijzigd door crisp op 06-05-2005 00:32 ]
Intentionally left blank