Grundzüge von Pattern-Matching und Split

Status
Nicht offen für weitere Antworten.

Wildcard

Top Contributor
Ein Wort vorweg:
Obwohl der StringTokenizer immer noch von vielen Programmieren verwendet wird, verzichte ich in diesem
Tutorial darauf, da er nur noch aus Gründen der Abwärtskompatibilität vorhanden ist, und generell durch
Code:
String.split()
ersetzt werden sollte.

Jeder kennt das Problem, man bekommt einen Datenstring und möchte diesen jetzt in verschiedene Felder zerlegen.
Gehen wir im ersten Beispiel von einem String aus der eine Person repräsentiert und der folgenden Aufbau hat:
[c]Vorname;Nachname;Wohnort;PLZ[/c]
Für solche einfachen 'zerlege-Aufgaben' ist die split()-Methode die einfachste Lösung.
In diesem Beispiel könnte das so aussehen:

Java:
String input = "Foo;Bar;Blupp;123456"
String[] results = input.split(";");
for (int i = 0; i < results.length; i++)
{
    System.out.println(results[i]);
}
split bekommt einen Paramter der bestimmt wo der String getrennt werden soll (vorsicht, split erwartet einen 'regulären Ausdruck' als Paramter, doch dazu später mehr), und liefert ein String[] in dem die Ergebnisse stehen.
Die Ausgabe hier würde also lauten:

Foo
Bar
Blupp
123456

Wie man sieht ist split eine sehr simple Möglichkeit um einen Datenstring anhand gewisser Trennzeichen zu teilen.
Will man kompliziertere Probleme (wie Beispielsweise eine Suchfunktion bei der Wildcards :wink: erlaubt sind) lösen,
verwendet man üblicherweise Pattern-Matching.

Pattern-Matching arbeitet mit sog. regulären Ausdrücken(regular Expressions auch schlicht RegEx genannt).
RegEx sind keine Java Eigenart, sondern auch in UNIX, Perl, C#... zu finden.
Unter einem RegEx versteht man in Java einen String der sich an eine spezielle Syntax hält.
Im kompletten nachzulesen ist diese Syntax unter Pattern. Ich werde in diesem Tutorial nur auf einige dieser Funktionen eingehen.

Als erstes werden wir nun versuchen das obige split beispiel mit Pattern-Matching zu lösen.
Dafür muss erst ein Pattern erstellt werden mit dem der Eingabe-String später verglichen werden soll:
Java:
Pattern pat = Pattern.compile("Regx");
In unserem speziellen Fall würde das so aussehen:
Java:
Pattern pat = Pattern.compile("\\w+");
Auf den ersten Blick sieht es etwas kryptisch aus, also hier die Erklärung:
[c]\w[/c] ist eine abkürzende Schreibweise, und steht für word-character, also das gleiche wie [c][A-Za-z_0-9] [/c]
(eckige Klammer bedeuten das jedes Zeichen darin erkannt wird, also würden alle Großbuchstaben von A-Z, die
Kleinbuchstaben, der '_' und die Ziffern 0-9 zu diesem Muster passen).
Warum also[c] \\w[/c] und nicht [c]\w[/c] ?
Der '\' ist in Java ein reserviertes Zeichen um Sonderzeichen wie 'newline', 'tabulator' usw... durch \n bzw. \t darzustellen.
Möchte man das in einem String ein ' \' steht, so muss der '\' maskiert werden => '\\'
Währe in unserem Beispiel das Trennzeichen statt des ';' ein '\' würde die Sache noch komplizierter.
Da '\' in Java und in der RegEx-Syntax als Maskierungszeichen dient, müsste dann doppelt maskiert werden:
[c]string.split(\\\\);[/c]

Und wofür das '+'
Es gibt sog. 'greedy quantifiers' mit denen die angegeben wird wie oft ein bestimmte Zeichen auftauchen können.
'+' einmal oder beliebig oft
'*' keinmal oder beliebig oft
'?' keinmal oder einmal
Für weitere quantifieres obigem Link folgen.

So, jetzt haben wir ein Pattern, aber wie gehts weiter?
Als nächstes brauchen wir einen Matcher:
Java:
Pattern pat = Pattern.compile("\\w+");
Matcher mat = pat.matcher(input);
Der Matcher wird also von unserem vorher kompilierten Pattern zurückgeliefert wenn wie dir matcher Methode mit dem input-String als Paramter aufrufen.

Der Matcher verfügt über 3 Hauptfunktionen um die Eingabe mit dem Muster zu vergleichen:

[c]mat.matches()[/c] liefert dann true, wenn der gesamte Eingabestring zum Muster passt.

[c]mat.find()[/c] liefert dann true wenn ein Teil der Eingabe zum Muster passt, merk sich die Ende des letzten treffers
und startet beim erneuten aufrufen von find() an dieser Position.

[c]mat.lookingAt()[/c] liefert true wenn ein Teil der Eingabe zum Muster passt, fängt jedoch immer am Anfang der Eingabe an.
d.h. um lookingAt mehrmals verwenden zu können muss nach jedem Treffer der matcher neu erzeugt werden, da er
sonst immer die gleichen Ergebnisse liefert.

Für unseren Zweck scheint hier also mat.find() die ideale Methode:
Java:
Pattern pat = Pattern.compile("\\w+");
Matcher mat = pat.matcher(input);
while(mat.find())
{
    //zur Bedeutung von mat.group(0) komme ich später
     System.out.println(mat.group(0));
}
Ergebnis:
Foo
Bar
Blupp
123456

Das Pattern sucht jetzt also den größten Teil des Strings der aus Buchstaben '_' und Zahlen besteht.

Das ist doch aber nicht das gleiche wie bei einem ';' zu trennen! Was ist mit Sonderzeichen?
Richtig! Wäre im input-String bspw. ein Name mit Umlauten würde dieses Pattern falsche Ergebnisse liefern.
Die korrekte Entsprechung für
Code:
string.split(";");
müsste so aussehen:
Java:
Pattern pat = Pattern.compile("[^;]+");
Matcher mat = pat.matcher(input);
while(mat.find())
{
    System.out.println(mat.group(0));
}

Wie oben beschrieben geben die '[' an, das jedes Zeichen darin erkannt wird.
'^' ist jedoch eine Negation, d.h. jetzt wird jedes Zeichen ausser ';' erkannt, was die Entsprechung von
Code:
string.split(";")
wäre.

Wofür soll das gut sein? Das ist ja viel komplizierter als split!!!
Komplizierter ja, aber dafür auch ungleich mächtiger, daher jetzt zur Erklärung dieser Zeile:
Java:
System.out.println(mat.group(0));

Eine nette Funktion des Matchers ist, das er über so genannte 'capturing groups' verfügt, die beim Erstellen des RegEx definiert werden:
Java:
Pattern pat = Pattern.compile("([a-zäöü]+);([a-zäöü]+);([a-zäöü]+);(\\d+)",Pattern.CASE_INSENSITIVE);
Matcher mat = pat.matcher(input);
if(mat.find())
   System.out.println(mat.group(3));

Ausgabe: "Blupp"

Alles was eingeklammert ist, stellt jetzt eine eigene Capturing-Group dar, und kann einzeln abgefragt werden.
Der matcher hat also 4 groups + die weiter oben verwendete
Code:
group(0)
die das Gesamtergebnis darstellt.
Zusätzlich habe ich noch
Code:
Pattern.CASE_INSENSITIVE
als Parameter übergeben, ich denke der Name ist selbsterklärend :wink:

Hier wird jetzt mat.matches verwendet (d.h. der gesamte String muss zum Muster passen), da jeder Datensatz diese Form haben soll.
Ein Name besteht eben nicht aus Zahlen, und in einer PLZ dürfen nur Ziffern enthalten sein.
Das ist eine Funktionalität die split nicht bieten kann.

Dieser Beitrag soll lediglich einen Einblick in die RegEx Welt liefern, um zu erkennen wie mächtig dieses package wirklich ist, ist wie so oft im Leben Eigeninitiative gefragt :###
 
Zuletzt bearbeitet von einem Moderator:
Status
Nicht offen für weitere Antworten.

Neue Themen


Oben