String zerlegen und Zwischenergebnisse in Array speichern

Hallo Experten,

man stelle sich vor, ich habe einen sehr langen String, verkürzt wie folgt:

XXXBBBXXXXXXXXXAAAXXXXXXAAAXXXXXXXXXXXXBBBXXXXXXBBBXXXXX

Von diesem soll bei jedem Vorkommen eines Suchmusters (hier: BBB oder AAA) der Anteil vor dem Suchmuster abgeknabbert werden und der Rest soll in ein Array geschrieben werden, sodass dieses am Ende folgenden Inhalt hat:

[0] =\> BBBXXXXXXXXXBBBXXXXXXBBBXXXXXXXXXXXXBBBXXXXXXBBBXXXXX
[1] =\> AAAXXXXXXBBBXXXXXXXXXXXXBBBXXXXXXBBBXXXXX
[2] =\> AAAXXXXXXXXXXXXBBBXXXXXXBBBXXXXX
[3] =\> BBBXXXXXXBBBXXXXX
[4] =\> BBBXXXXX

Hierbei spielt die Reihenfolge keine Rolle. Ich habe es so gelöst, dass ich den String ($string) nach dem Suchmuster ($pattern) unter Erhaltung des Suchmusters zerlege und danach aus den Fragmenten ($fragments) von der rechten Seite ausgehend die gesuchten Teilstrings ($fragmentsNew) wiederherstelle:

$string = "XXXBBBXXXXXXXXXAAAXXXXXXAAAXXXXXXXXXXXXBBBXXXXXXBBBXXXXX";
$pattern = "BBB|AAA";
$fragments = preg\_split("/($pattern)/", $string,-1,PREG\_SPLIT\_DELIM\_CAPTURE);
$numberOfFragments = count($fragments);
for($i = $numberOfFragments-1; $i\>1 ; $i=$i-2){
   $fragmentsNew[$i/2-1] = $fragments[$i-1].$fragments[$i];
   $fragments[$i-2] = $fragments[$i-2].$fragmentsNew[$i/2-1];
}

Im Testsystem funktioniert das prima. Nach dem kompletten Durchlauf des FOR-Loops enthält das Array genau die gesuchten Teilstrings, auch wenn der Originalstring im realen Anwendungsfall bis zu mehrere Millionen Zeichen lang sein kann. Je nach String und Suchmuster erhielt ich die Fehlermeldung, dass zu viel Speicher verbraucht wurde, und das Skript brach ab. Dem konnte ich teilweise entgegenwirken, indem ich das memory_limit in der php.ini auf 2048M hochgesetzt habe. Bei einer gewissen Anzahl der Fragmente ($numberOfFragments), die ich durch probieren ermittelt habe, generiere ich jetzt selbst eine Fehlermeldung und führe den FOR-Loop nicht aus. Wenn es funktioniert (was es jetzt mit der o.g. Einschränkung eigentlich auch tut), dauert die Ausführung je nach Suchmuster von einigen Sekunden bis zu einer guten Minute (mit dem ganzen Rest, der hier nicht aufgeführt ist).

Nun meine eigentliche Frage: Hat jemand eine Idee, wie das Obige speichersparender und/oder schneller bewerkstelligt werden kann, sodass ich evtl. das Limit der Fragmentanzahl hochsetzen könnte oder das Skript nicht ganz so lange braucht?

Viele Grüße
Huttatta

Hallo , die frage ist, was den out of limit erzeugt ?
schrittweise das string kopieren ?

apos = 0;
$pattern[] =‚AAA‘;
$pattern[] =‚BBB‘;
$stringende = strlen($string);

while ( $stringende > $fullpos ) {
$posing = $stringende;
$pospatter =’’;
foreach ($pattern as $pat ) {
$newposing = stripos($pat,apos);
if ($newposing >= $posing ) {} else { $posing = $newposing; $pospatter = $pat; }
}
$fullpos = $posing+strlen($pospatter);
$found[] = substr($string,apos,$fullpos)
apos = $fullpos + 1;
}

Hallo TechPech,

Hallo , die frage ist, was den out of limit erzeugt ?
schrittweise das string kopieren ?

danke für Deine Antwort und den Vorschlag. Ja, der Fehler wurde durch das Kopieren verursacht. Ich habe inzwischen die Anforderungen an die Stringzerlegung überdacht und festgestellt, dass die Länge der Ergebnis-Strings auf ein sinnvolles Maß begrenzt werden kann. Das spart im Extremfall grob geschätzt 90% Speicher. Im ersten Durchgang gehe ich statt mit preg_split() jetzt auch mit substr() heran, wobei ich in der Weiterverarbeitung der Ergebnisse jedoch beim preg_split()-Algorithmus bleibe. Einen großen Geschwindigkeitszuwachs oder -verlust konnte ich nicht feststellen. Um das Skript deutlich schneller zu machen, dafür gibt es jedoch noch andere Stellschrauben zum drehen.

Viele Grüße
Huttatta

die von dir gesuchte datenstruktur sieht aussergewöhnlich aus, deshalb würde ich die schonmal grundsätzlich in frage stellen.

das problem ist halt, dass du abhaengig von einer gewissen häufigkeit von trennzeichen riesige strukturen aufbaust. das kriegst du nie stabil.
den ganzen vorgang „strings im speicher umherkopieren“ - kostet auch zeit - würde ich deshalb vermeiden und mir nur die positionen der delimiter merken. das geht ja auch mit preg_split. dann kannst du mit einer funktion und substr immer direkt den gesuchten string vom original holen

Hallo Jörg,

die von dir gesuchte datenstruktur sieht aussergewöhnlich aus,
deshalb würde ich die schonmal grundsätzlich in frage stellen.

Um den Rest des Codes erst mal fertig zu schreiben, bleibe ich vorerst bei der ursprünglichen Methode, die mit den vorgenommenen Einschränkungen nicht besonders flott aber stabil und korrekt läuft. Aber du hast absolut Recht, bei der Datenmenge muss man anders rangehen, und das werde ich auch tun. Die Idee, nur die Delimiter zu merken ist natürlich klasse.

Ich löse das nun mit preg_match_all(). Ein kleines Problem ist folgendes. Ich erhalte ein mehrdimensionales Array ($pregMatchAll), in dem zweimal das Gleiche steht. Hierbei ist $pregMatchAll[0] identisch mit $pregMatchAll[1]. Die Suchergebnisse werden also komplett zweimal in das Ergebnis-Array ($pregMatchAll) geschrieben. Hier ist ein Beispiel:

$string = "XXXAAAXXXXXXBBBXXXXXXAAAXXXCCCXXXXXBBBXXXDDDXXXBBBXXCCCX";
$search = "AAA|BBB";
preg\_match\_all("/($search)/",$string,$pregMatchAll,PREG\_OFFSET\_CAPTURE);
print\_r($pregMatchAll);

Dieses Skript führt zu folgender Ausgabe:

Array
(
 [0] =\> Array
 (
 [0] =\> Array
 (
 [0] =\> AAA
 [1] =\> 3
 )
 [1] =\> Array
 (
 [0] =\> BBB
 [1] =\> 12
 )
 [2] =\> Array
 (
 [0] =\> AAA
 [1] =\> 21
 )
 [3] =\> Array
 (
 [0] =\> BBB
 [1] =\> 35
 )
 [4] =\> Array
 (
 [0] =\> BBB
 [1] =\> 47
 )
 [5] =\> Array
 (
 [0] =\> AAA
 [1] =\> 57
 )
 )
 [1] =\> Array
 (
 [0] =\> Array
 (
 [0] =\> AAA
 [1] =\> 3
 )
 [1] =\> Array
 (
 [0] =\> BBB
 [1] =\> 12
 )
 [2] =\> Array
 (
 [0] =\> AAA
 [1] =\> 21
 )
 [3] =\> Array
 (
 [0] =\> BBB
 [1] =\> 35
 )
 [4] =\> Array
 (
 [0] =\> BBB
 [1] =\> 47
 )
 [5] =\> Array
 (
 [0] =\> AAA
 [1] =\> 57
 )
 )
)

Ist das normal? Ich dachte, das Ergebnis soll eigentlich nur einmal in $pregMatchAll[0] erscheinen und nicht noch einmal in $pregMatchAll[1] wiederholt werden. Gibt es einen Trick, um das zu verhindern bzw. habe ich evtl. einen Parameter bei preg_match_all() übersehen? Ich schreibe das Array deshalb noch einmal um, sodass es zwar die gleiche Struktur hat, aber nur noch $pregMatchAll[0] enthält. Damit kann ich weiterarbeiten. Schöner wäre jedoch, wenn es gleich „richtig“ wäre und ich auf den Umschreibschritt verzichten könnte.

Gruß
Huttatta

$string =
„XXXAAAXXXXXXBBBXXXXXXAAAXXXCCCXXXXXBBBXXXDDDXXXBBBXXCCCX“;
$search = „AAA|BBB“;
preg_match_all("/($search)/",$string,$pregMatchAll,PREG_OFFSET_

Ist das normal? Ich dachte, das Ergebnis soll eigentlich nur
einmal in $pregMatchAll[0] erscheinen und nicht noch einmal in
$pregMatchAll[1] wiederholt werden. Gibt es einen Trick, um
das zu verhindern bzw. habe ich evtl. einen Parameter bei
preg_match_all() übersehen?

naja du hast heimlich eine capturing group notiert - die runde klammer. die in deinem falle nur das oder begrenzen soll, was du nciht brauchst, weil du nix davor und nix dahinter hast. die group ist das 1. element.
das 0. element enthaelt immer den gesamten match. in deinem falle ist der aber konstant - du hast keine wildcards drin - und ist gleich dem capturing ausdruck.

mögliche lösungen:
/aaa|bbb/ - ohne capturing group
/(?:aaa|bbb)/ - non capturing group

probier den noch mal zum testen, damit du den unterschied in der ausgabe siehst:
/(aaa|bbb)./

http://perldoc.perl.org/perlretut.html#Non-capturing…

1 Like

Hallo Jörg,

das 0. element enthaelt immer den gesamten match. in deinem
falle ist der aber konstant - du hast keine wildcards drin -
und ist gleich dem capturing ausdruck.

oh, na klar. Das Beispiel ist ausschließlich für die Frage hier im Forum sehr einfach gehalten. In Wirklichkeit kommen eventuell tatsächlich noch Wildcards hinzu. Das hängt davon ab, mit welchen Suchsparametern die Funktion aufgerufen wird. Weil aber die Datenmenge im Ergebnis bei dieser Methode quasi nichts im Vergleich zu vorher ist, kann ich getrost ohne Fallunterscheidung das Element [0] in ein neues Array kopieren oder darüber hinausgehende Elemente löschen, ohne eine signifikante Verlangsamung oder Speicherprobleme erwarten zu müssen.

Vielen Dank, Jörg! Du hast mir wirklich sehr geholfen.

Gruß
Huttatta