PHP Regex: Text am Satzende trennen

klausschreiber

Well-known member
ID: 162475
L
6 Mai 2006
247
8
Hallo,

ich möchte einen Text am Satzende trennen bzw. in ein Array umwandeln. Normalerweise könnte man da ja einfach den Punkt, das Fragezeichen und das Ausrufezeichen hernehmen. Problematisch sind jedoch Abkürzungen, sowie so Sachen wie 14. Mai usw.

Beispieltext:
Das ist ein Test. Ich mache hier viele Testz. z.B. gibt es hier viele Sachen, über die ich z.B. schreiben könnte. Heute haben wir den 14. Mai. Ist das wirklich wahr? Ich glaube schon! Der Preis beträgt 19 Euro inkl. Mwst. und sonstigen Kosten./QUOTE]

Optimales Ergebnis:
PHP:
satzteil[0] = "Das ist ein Test."
satzteil[1] = "Ich mache hier viele Testz."
satzteil[2] = "z.B. gibt es hier viele Sachen, über die ich z.B. schreiben könnte."
satzteil[3] = "Heute haben wir den 14. Mai."
satzteil[4] = "Ist das wirklich wahr?"
satzteil[5] = "Ich glaube schon!"
satzteil[6] = "Der Preis beträgt 19 Euro inkl. Mwst. und sonstigen Kosten."

Theoretisch würde es mit preg_split und lookbehinds funktionieren:
PHP:
$pattern = '~(\!|\?|(?<! z| z\.b| inkl| mwst|[0-9])\.)~is'; 
$satzteil = preg_split($pattern, $text, -1, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
Das Problem ist, dass in lookbehinds manche Dinge nicht funktionieren. Ich schaue immer, ob vor der Abkürzung ein Leerzeichen ist, damit nicht fälschlicherweise ein Wort als Abkürzung erkannt wird. (Nehmen wir an, "tz." ist eine Abkürzung, dann soll in "Ich schreibe einen Satz." das "tz." trotzdem nicht als Abkürzung erkannt werden.
Der beste regex innerhalb des lookbehinds wäre " (z|z\.b|inkl|mwst|[0-9]+)" (Leerzeichen am Anfang). Aber sowohl "x(ab|cde)" also auch "[0-9]+" Konstruktionen sind ja im lookbehind nicht erlaubt.
Meine Lösung ist ja " z| z\.b| inkl| mwst|[0-9]", also vor jeder Abkürzung ein Leerzeichen, vor Zahlen jedoch nicht, weil eine Zahl ja auch aus mehr als einer Ziffer bestehen kann.

Es funktioniert zwar, aber optimal ist es irgendwie nicht.
Zweiter Versuch war mit preg_match_all und "[^.?!]+( (z\.B\.)|(inkl\.)|[0-9]+)*\.". Leider interpretiert er da die Abkürzungen da trotzdem als Satzende.

Gibt es irgendwie eine bessere Lösung, als ich sie mit preg_split habe?

Was ich mir sonst noch überlegt habe, wäre alle Wörter mit einem Punkt rauszufiltern, dann in einem Array oder einer Datenbank zu schauen, ob es eine Abkürzung ist und falls nicht, das Leerzeichen nach dem Punkt durch ein "new line" auszutauschen per
PHP:
string[x] = "\n";
und danach dann halt per explode zu trennen.

Da es ja vermutlich recht viele gängige deutsche Abkürzungen gibt, die einen Punkt enthalten, ist es überhaupt sinnvoll, das per regex (ab|cd|ef) zu machen oder ist eine Suche in der Datenbank da sowieso schneller?


Danke und Gruß,
Klaus
 
Moin.

Das Problem kommt mir bekannt vor :biggrin: Hatten wir an der Uni in Textmining auch, wobei das nicht das eigentliche Problem war, sondern nur noch zusätzlich die Sache verkompliziert hat.

So als kleinen Anstoß, dass es sicherlich nicht trivial is, Sätze korrekt zu erkennen:
Ich finde, deine Optimallösung ist falsch.
Ich mache hier viele Testz. z.B. gibt es hier viele Sachen, über die ich z.B. schreiben könnte.
is für mich nur ein Satz - wenn auch ein nicht besonders "deutscher" Satz. "z.B." kann nie Satzanfang sein. Es müsste da dann nämlich "Z.B." heißen. (Die Problematik, dass selbst das immer noch falsch is und eigentlich Z. B. sein muss, sparen wir uns hier mal :mrgreen:)

Bei uns damals war die Aufgabenstellung, Namen in Veranstaltungstexten zu erkennen. Schön, wenn nach dem Wort (= durch Leerzeichen separierte Zeichen) "Prof." zwei großgeschriebene Wörter kommen; dann kann man schon mal von einem Vor- und einem Nachnamen ausgehen.

Aber auch hier is die Problematik des Satzendes drin:
(1) Ich sah gestern Prof. Hans Smith.
(2) Ich sah gestern den Prof. Einige Tests hatte er diesmal auch mit dabei.
Kannst dir ja vorstellen, was erkannt wurde :wall: Und warum der Fehler? Weil in Text (2) nicht gerallt wurde, dass das zwei abgeschlossene Sätze sind und nicht "Prof." das Wort is, sondern "Prof" plus Satzende-Punkt.

Um effektiv ein Satzende zu erkennen, musst du den Text verstehen - zumindest den Satzbau analysieren. In Text (1) ist es eindeutig, dass der Punkt am "Prof" ein Abkürzungspunkt is, da die zwei Wörter "Hans Smith" kein Satz sind. In Text (2) merkst du dass das großgeschriebene Wort ein Adjektiv is und folglich an einem Satzanfang stehen sollte; ergo war der Punkt vorher ein Satzende-Punkt.
 
Danke für die Antwort

ok, "z.B." am Satzanfang war mein Fehler:biggrin:.

Bezüglich Abkürzungen am Satzende:
Das ist mir zwar auch schon aufgefallen, aber ich habe mir gedacht, erstmal das eine Problem lösen, bevor man das nächste angeht:).
Ich will die Sätze in einem späteren Schritt sowieso noch analysieren nach Substantiv, Verb usw. Wenn ich dann merke, dass mit dem Satz etwas nicht passt, kann ich ihn ja dann nochmal trennen. Vielleicht bin ich da auch auf dem falschen Weg, aber ich hatte eigentlich gedacht, dass es einfacher ist, den Text zu analysieren, wenn er in Sätze aufgeteilt ist.

Wenn ich mir aber jetzt die Probleme anschaue, überlege ich mir, ob es nicht vielleicht doch der falsche Weg ist?

Oder habt ihr in der Uni irgendeinen Trick gefunden, das Problem "einfach" zu lösen?

Gruß,
Klaus
 
ok, "z.B." am Satzanfang war mein Fehler:biggrin:.
Du glaubst wohl nicht, dass du der einzige bist, der das falsch macht.

Wie gesagt, der Fehler, zwischen den beiden Wörten kein (geschütztes) Leerzeichen zu machen, ist viel verbreiteter. Ich mach das sogar selber hier im Forum, da es keine Möglichkeit für ein geschütztes Leerzeichen gibt - das Forum wandelt die automatisch in normale Leerzeichen um :evil:
Oder habt ihr in der Uni irgendeinen Trick gefunden, das Problem "einfach" zu lösen?
Wir haben das Rad nicht neu erfunden und einen vorhandenen POS-Tagger verwendet. Allerdings haben wir uns nur einige Stunden mit diesem Problem beschäftigt und somit das Problem mit der Worttrennung eher vernachlässigt.

Was bringt dir eine hohe Precision, wenn der Recall drunter leidet? - Gilt natürlich umgekehrt analog.


Ich weiß ja nicht, was konkret dein Ziel is - also was du mit den separierten Sätzen anfängen willst -, aber ich würde mich einfach auf die bekanntesten Abkürzungen verlassen, diese fest vorgeben und entsprechend so vorgehen.

Bei Zahlen ist selbes Risikospielchen: Die Chance, dass ne Zahl am Satzende steht, is gering, v.a. (<- siehste: schon wieder ein Fehler) weil Zahlen bis einschließlich der Zwölf ja eh ausgeschrieben werden sollten. Also einfach immer davon ausgehen, dass die Zahl zu was nachfolgendem gehört.

Du kannst ja selber testen, wie gut es funktioniert und ob die Fehlerquote akzeptabel is.
 
Danke für deine Antwort.

POS-Tagger kannte ich noch nicht (bzw. den Begriff). Ich habe mal den TreeTager von der Uni Stuttgart ausprobiert, aber der hat bei einer Abkürzung am Satzende auch das Satzende nicht erkannt.

Ich werde es jetzt wohl erstmal so machen, wie du vorgeschlagen hast. Mir geht es ja auch erstmal vor allem um so Abkürzungen wie "z.B.", "u.a.", ... und nicht so was wie "nördl." oder Ähnliches. Ein Problem ist halt usw. und etc., aber da werde ich dann wohl einfach erstmal festlegen, dass die am Satzende stehen.

Dann bleibt aber trotzdem noch die Frage, wie ich das am geschicktesten mache. Ich denke, es ist wirklich geschickter, alle Wörter mit einem Punkt rauszufiltern und dann zu schauen, ob sie eine Abkürzung sind. Ich denke, das ist um einiges übersichtlicher als alle Wörter in einen Regex zu schreiben, oder?


Gruß,
Klaus
 
Dann bleibt aber trotzdem noch die Frage, wie ich das am geschicktesten mache.
Ich würde in so einem Fall vom RegExp weggehen. Das wäre mir zu komplex und lieber hab ich hundert Zeilen mehr Code und das Programm braucht zweimal länger, hab dafür aber den Vorteil, einen einfacheren Code zu haben und auch meine Daten in einer einfacheren Struktur, als das RegExp-Rumgedoktere.

Mein Vorgehen wäre, den Text in Tokens zu zerlegen. Token in diesem Fall Wörter. Die kannst du dann wenn gewünscht direkt in den POS-Tagger werfen, wenn du das willst.
Wie dus konkret abspeicherst, musst du dir überlegen, wie du dann am besten drüber willst. Einfach Leerzeichen oder Interpunktionszeichen als Delimiter zu benutzen, wird halt bei Abkürzungen mit mehr als einem Punkt böse.

Ob du das so genau brauchst... wenn du nur die Satzgrenzen willst, dann halt primitiv drüber mit
PHP:
$sentences = array();
for($i = 0, $s = 0; $i < strlen($text); $i++)
  if($text[$i] == '.' || $text[$i] == '?' || $text[$i] == '!')
  {
    $sentences[] = substr($text, $s, $i-1);
    $s = $i;
  }
Und hier musst du halt im Falle des potentiellen Satzendes (if-Block) vorher gucken, ob wirklich ein Satzende is, oder ob z.B. substr($text, $i-1, 4)=='z.B.' und es weitergehen soll.
 
Zuletzt bearbeitet:
Man könnte auch folgendermaßen vorgehen.
Man untersucht die Länge des Strings zwischen 2 Punkten.

Längen x [0...y]
0 = ein Wort mit mind. 2 Punkten, also kein Ende
1 = (z.B.)
...

Ab einer bestimmten Länge handelt sich dann einfach um einen Satz.
Da könntest Du halt ein wenig experiementieren.

Viel Spaß mit diesem Ansatz.

Nachtrag:
Beispiel zu dem Thema!

Satz: "Ich bin doof."

ist ein sehr kurzer aber korrekter deutscher Satz, der Länge 12 (mit "." 13).
Wer einen kürzeren Satz kennt, kann ihn gerne mitteilen. Also würde alles was länger als 12 ist, ein Satz sein. Gar nicht so schwer oder ?
 
Zuletzt bearbeitet:
danke für eure Antworten.

@theHacker:
so werde ich das jetzt auch erstmal machen, dass ich mir per Schleife Buchstabe für Buchstabe anschaue und Wörter mit Punkt mit Abkürzungen vergleiche. Ich bin gerade dabei, dafür einen Code zu schreiben und dann kann ich es ja mal mit Wikipedia-Artikeln oder so ausprobieren.

@tobomator:
"Normale" Sätze, solange sie nicht nur aus einem Word bestehen (wobei ich nicht weiß, ob sie dann überhaupt als deutscher Satz durchgehen?), sind wohl nicht kleiner als 12 Zeichen. Aber nur, weil sie länger sind, muss es ja noch nicht unbedingt ein Satz sein.
Beispiel:
Ich schaue mir sehr gerne Sportsendungen im Fernsehen an, jedoch keine Rennsportarten wie die Formel 1, weil ich u.a. denke, dass dadurch nur unnötig die Umwelt verschmutzt wird.
Der erste Punkt kommt erst nach ca. 100 Zeichen bei "u.a.", dennoch ist der Satz noch nicht fertig. In diesem Fall sind zwar Kommas dazwischen, die man zur Abgrenzung benutzen könnte, aber gibt ja auch längere Sätze bis zum Punkt ohne Komma.
Ich liebe meine besonders teuren Gummistiefel u.a. deswegen, weil sie so bequem sind.
Oder habe ich die falsch verstanden?


Gruß,
Klaus
 
"Normale" Sätze, solange sie nicht nur aus einem Word bestehen (wobei ich nicht weiß, ob sie dann überhaupt als deutscher Satz durchgehen?), sind wohl nicht kleiner als 12 Zeichen.
Nicht? Doch, sehr wohl. Natürlich. Immer doch.
...um mal eine kleine Auswahl zu bieten :biggrin:
 
Ich möchte mich gerne mit Ich bin alt anschließen.
Aber das, was theHacker sagt ist soweit ich weiß nicht ein deutscher Satz, sondern direkte Rede. Ein dt. Satz besteht aus Subjekt und Prädikat. Somit wäre Ich esse auch ein Satz, der v.a. :))) kleiner ist als 12 Zeichen.


edit ~ hab grad bei Wiki nachgelesen, was so allgemeine Satzdefinitionen sind. Mein (veraltetes) Wissen ist noch aus der Grundschule :D
 
Das geht schief. Man kann weder an Wort-/Buchstabenanzahl oder Wort-/Satzlänge irgendwas ablesen.
 
Ich schaue mir sehr gerne Sportsendungen im Fernsehen an, jedoch keine Rennsportarten wie die Formel 1, weil ich u.a. denke, dass dadurch nur unnötig die Umwelt verschmutzt wird.

Also hier kann man doch meinen Anstz wunderbar nutzen.
Du meinst natürlich, er bricht dann bei u.a. ab.
Dann solltest du Dir folgendes dazubauen, was ich eigentlich vorrausgesetzt habe.

Position des letzten Satzendes merken in $a
in $b und $c wird das neue Satzende abgelegt.
$c fungiert als "blindcopy" und $b wird immer nur mit neuem Satzende überschrieben aktuell also. $c wird nur angeglichen auf den wert von $b, wenn ein deutscher satz gefunden wurde.
Findet man nun einen Satz, wie oben beschrieben, würde also in $a 0 stehen.
$b und $c enthalten dann position vom "." hinter u.
Nun wird der algorithmus feststellen, oh satz mit der länge 1 folgt ...

Was folgt daraus, $c wird angeglichen auf $b. Gibt es nun eine Differenz zwischen $c und $b die der mindestsatzlänge entspricht, ist ein Satz zwischen $a und $c zu finden.

War doch nicht schwer oder ?

theHacker schrieb:
Das geht schief. Man kann weder an Wort-/Buchstabenanzahl oder Wort-/Satzlänge irgendwas ablesen.

Nein man müßte wohl Deiner Meinung nach noch nach Subject und Prädikat suchen, was in Deinem Satz "Natürlich." nicht ganz geht.

Es gibt mehrere Ansätze um etwas zu lösen. Man sollte diese immer Mixen, damit man auf der sicheren Seite ist.
Am besten fragt man einen Deutschlehrer. Der weiß, welche Regeln für einen deutschen Satz und dessen Bau gelten...
 
Zuletzt bearbeitet:
so, war die letzten Tage leider sehr beschäftigt, daher erst jetzt die Antwort.
Erstmal danke für eure Antworten. Habe es jetzt nach dem Vorschlag von theHacker umgesetzt und es funktioniert eigentlich schon ganz gut. An ein paar Feinheiten muss ich noch feilen.

@tobomator:
In meinem Beispiel hatte die Abkürzung ja zwei Punkte, aber der Satz kann ja auch folgendermaßen lauten:
Ich schaue mir gerne die Wissenssendung auf RTL an, weil dort der berühmte Prof. Eberhart super ausführlich erklärt, wie die Welt aufgebaut ist

Gruß,
Klaus
 
Erweiter meinen Ansatz um folgenden Punkt:

Du hast ein Satzende gefunden, dann prüfe ob das Wort vor dem Satzeinde eine Abkürzung ist. Somit wäre eigentlich alles andere vorher hinfällig.

Wenn ein Prof. oder ein Dr. oder sonstiges vor dem Punkt steht, weißt Du, es ist eben kein Satzende!
So schwer kann man es sich doch gar nicht machen
 
klar kann man das Prüfen, aber dann bräuchtest du ein Array mit ALLEN Abkürzungen (oder zumindest mit den häufigst-verwendeten). und dann sind Immer noch Möglichkeiten, diese zu umgehen.

Ich denke es ist ausreichend, wenn überprüft wird, ob ein Wort nach dem Punkt mit einem großen Buchstaben anfängt und ob nach dem Punkt ein Leerzeichen eingefügt ist. Damit sollte man ausreichend gearbeitet haben. Will man Unbedingt "Prof. " verwenden, dann müsste man halt einen Key definieren, der erst vor der Ausgabe zu einem Leerzeichen umgewandelt wird, oder eine Umgebung definieren, die nicht getrennt werden darf! geht alles mit Regex!
 
Ich denke es ist ausreichend, wenn überprüft wird, ob ein Wort nach dem Punkt mit einem großen Buchstaben anfängt und ob nach dem Punkt ein Leerzeichen eingefügt ist.
Damit würdest du hier im Forum z.B. monumental scheitern :LOL:
Die Groß-/Kleinschreibung beherrschen die wenigsten und wenn du dann noch n Klemper dabei hast, wirds noch schlimmer.
klar kann man das Prüfen, aber dann bräuchtest du ein Array mit ALLEN Abkürzungen (oder zumindest mit den häufigst-verwendeten).
Das dürfte weniger ein Problem sein. Wörterbücher gibts genug, z.B. https://de.wikipedia.org/wiki/Liste_der_Abkürzungen
 
danke für eure Antworten. Ein Array mit Abkürzungen ist kein Problem. Das habe ich ja auch jetzt bei der Lösung von theHacker.

@tobomator:
Das ist eine Lösung, aber dann kann ich ja gleich einfach bei jedem Punkt, wo danach ein Leerzeichen kommt, prüfen, ob es eine Abkürzung ist, wie ich es bisher umgesetzt habe. Das Problem:
Das ist mein Prof. Ich finde ihn sehr gut
kann ich damit auch nicht besser lösen. Vielleicht baue ich aber noch ein, dass alles unter 4 Buchstaben eine Abkürzung ist oder so, das kann ich in der Tat machen.

Gruß,
Klaus
 

Ähnliche Themen