RegEx Kurs: Pascal

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
TuxPeter
Beiträge: 1762
Registriert: 19.11.2008 20:39:02
Lizenz eigener Beiträge: MIT Lizenz

RegEx Kurs: Pascal

Beitrag von TuxPeter » 26.06.2022 11:15:29

Kursübersicht

1. Allgemeines und ein Beispiel

Laut [1] gibt es, nach einer längeren Pause seit 2019, wieder eine Betreuung und Weiterentwicklung der Pascal RegEx-Unit. Es gibt für FreePascal noch weitere Engines, die aber aktuell nicht mehr unterstützt werden. Diese Wiki-Seite liefert auch das Beispiel, von dem ich ausgegangen bin, hier die wesentliche Information der verlinkten Seite:

Code: Alles auswählen

Mit der RegExpr von Sorokin ist es sehr einfach zu überprüfen, ob ein Ausdruck (expression) in einer Zeichenkette (string) vorkommt.
Dazu erstellen Sie eine Instanz von TRegExpr. Dann übergeben Sie Ihren regulären Ausdruck der Eigenschaft
 TRegExpr.Expression. Um den Regulären Ausdruck zu überprüfen führen Sie das Ganze mit der Methode 
 Exec aus. Wenn der Reguläre Ausdruck in der Zeichenkette (String) vorkommt, dann gibt die Methode Exec 
 den Wert True (Wahr) zurück.

var
  RegexObj: TRegExpr;
begin
  RegexObj := TRegExpr.Create;
  RegexObj.Expression := '.*login.*';
  if RegexObj.Exec('Bitte finde das Wort login in diesem Satz.') then WriteLn('Das Wort login wurde im Satz gefunden!');
  RegexObj.Free;
end;
Weitere, ausführlichere Informationen über den Umfang der Implementation, der auch unsere bisher behandelten Ausdrücke abdeckt, finden sich unter [2]

Sehr empfehlenswert finde ich die Doc [3] vom Betreuer selbst, sie ist auch in Deutsch [4] erhältlich, aber leider nur unvollständig übersetzt.

[1] https://wiki.freepascal.org/RegEx_packages
[2] https://wiki.lazarus.freepascal.org/IDE ... xpressions
[3] https://regex.sorokin.engineer/en/lates ... sions.html
[4] https://regex.sorokin.engineer/de/lates ... sions.html

2. Praktisches

Wer FreePascal noch nicht auf dem Computer hat - apt install fpc
schaufelt das Benötigte auf die Kiste.
Sinnvollerweise legt man ein Verzeichnis $HOME/pas/ für alles Weitere an.
compiliert und gleichzeitig gelinkt wird mit fpc progname.pas
was eine Objektdatei progname.o sowie ein ausführbares Progr. mit dem Namen progname mit passenden Rechten erzeugt. Vermutlich ist nach ~/pas/ kein Exec-Pfad gelegt, deshalb Programmstart mit ~/pas/progname

Für die Bearbeitung der Sources taugt ein beliebiger Editor, selbstDebian Mousepad macht ein akzeptables Syntax-Highlightning. Wer an die alte Turbo-5ff-Oberfläche erinnert werden möchte, kann auch einfach fp starten; muss aber erst den Bug beseitigen, der hier [5] behandelt wurde, sonst werden keine Units gefunden.
... im IDE-Menü "Options/Directories/Units"
"/usr/lib/x86_64-linux-gnu/fpc/3.2.0/units/x86_64-linux/*/" gesetzt
Dann funktioniert zumindest das Compilieren, ich hatte dann aber noch weitere Schwierigkeiten bekommen, denen ich nicht nachgegangen bin.

[5] viewtopic.php?p=1303421#p1303363

Schließlich gibt es natürlich noch die alles-erschlagende Oberfläche Lazarus, die extra installiert werden muss.

Ich nutze seit einem Tipp hier vom Debian-Forum DebianGeany; flott, klein, übersichtlich, und es bringt für die fpc-Compilierung auch gleich die passenden Einstellungen mit.
Ansonsten unter Erstellen -> Kommandos zum Erstellen konfigurieren -> kompilieren >fpc "%f"< eintragen

Zum 1. Programm

NoPaste-Eintrag41754

Wir sehen, dass (das Objekt) re am Anfung "created" und gleichzeitig eine RegExpr. zugewiesen wird. Der Aufruf re.Exec mit dem zu durchsuchenden String liefert TRUE, wenn mindestens ein Treffer vorhanden ist. Dieser Match wird per WriteLn (mit Zeilenvorschub) geschrieben und in der While-Loop nach weiteren Matches gesucht. Am Schluss wird das Objekt wieder freigegeben. Also klassisches, prozedurales Pascal, auch wenn re einen Objekt-Typ darstellt.

Arbeitsvorschläge:

den RE so ändern, dass
1) alle "l" ausgeben werdem
2) nur das 2. "hello" finden und zusammen mit den nachfolgenden 2 Zeichen ausgeben (damit man sieht, dass es tatsächlich das 2. "hello" ist).
3) Was ist der Unterschied in der Behandlung der Zeichen IN der inneren Klammer "()" und außerhalb dieser Klammer?

2. Programm

-jetzt ohne vorgegebene Lösung: NoPaste-Eintrag41763

Hier wurde das Beispiel in eine Prozedur verpackt. Die Matches werden weiterhin einzeln ausgegeben, allerdings jetzt ohne Zeilenvorschub, und zwecks besserer Übersicht außerdem durch einen Unterstrich getrennt. Erst am Ende gibt es einen Zeilenvorschub.

Es folgen im Hauptprogramm einige Aufrufe dieser Prozedur. Leider matcht die RE im Beispiel immer den ganzen zu durchsuchenden Text, Eure Aufgabe bestehen also darin, statt des All-Matchers ".*" die jeweils passende RE einzutragen.

Wenn jemand mit diesem Programm weitere Aufgaben erarbeitet / ertestet - immer her damit!

Nun folgt ein Programm zum Durchsuchen von Textdateien
neue Version: NoPaste-Eintrag41769
alte Version: NoPaste-Eintrag41757

Die Prozedur FindMatches schreibt jetzt nicht mehr direkt auf den Bildschirm, sondern "addiert" (concateniert) die Matches auf einen Ausgabe-String.
Für die Ausgabe gibt es zwecks Übersichtlichkeit eine eigene kleine Procedure. Das Hauptprogramm macht erst mal einen Plaustest, liest dann die Textdatei sequentiell und lässt die Zeilen in "Find_and_write" weiterbearbeiten, welches sie dann an FindMatches weitergibt. Ich vermute, dass meine Kommentare zum Verständnis ausreichen, ansonsten einfach nachfragen.

Wegen der zu übergebenden Kommandozeilen-Parameter ist es empfehlenswert, das Prog in einem Terminal ablaufen zu lassen, ansonsten müssten die Params in die IDE hineingefummelt werden. Zum Ausprobieren verwende ich wieder die famose schwaebische Kunde; also z.B. alle "sch" einschließlich zweier Folgezeichen geht so:
___________________

Code: Alles auswählen

~/pas/test_regex "(sch..)" schwaebische-kunde.txt 
  1 sche 
  2<Leerzeile>
  9 sche 
 11 schwa
 15 schwa
 22 sche 
 23 schie
 30 schwa
 31 schen
 37 schwi
 46 schni
 47 schaa
______________________
Das Verhalten ist somit dem egrep -no "(sch..)" schwaebische-kunde.txt sehr ähnlich, außer der Anzeige der Leerzeile, die ja bei Bedarf auch aus dem Progr. leicht heraus genommen werden kann. Ich fand es interessant, den Output jeweils mit egrep zu vergleichen. Und ach ja, die RE muss gequotet werden, sonst meckert die bash.

Weitere Arbeitsvorschläge:

- Finde alle Auslassungszeichen "'"

- alle Auslassungszeichen die einem b folgen, das b selber aber nicht.

- jetzt mal etwas pfiffiger: Zeige alle Sätze, die den Buchstaben D enthalten (groß oder klein egal) bis genau zum ersten Vorkommen und einschließlich dieses Zeichens.

- zeige alle Wörter einzeln unter Verwendung der Zeichenklasse \w, was dem [:alpha:] im egrep entspricht.
- zeige alle Sonderzeichen und vergleiche "^\w" mit "\W"

Falls ihr Lust dazu verspürt - denkt Euch weitere Aufgaben aus, und stellt sie vor.

Zum Schluss noch ein kleines Anwendungsbeispiel, welches eine Datei, die Dateinamen von Bildern enthält, mittels Regex durchsucht. Diese kann z.B. mit find erzeugt werden. Die Treffer werden mit einem Bilder-Viewer angezeigt, der ristretto hat sich als recht geeignet erwiesen.

NoPaste-Eintrag41758

Ich denke, ich werde das Beispiel in eine für meine Bildersammlung angepasste Anwendung ausbauen, dies ist nur ein „proof of concept“ – so heißt das wohl heute, glaub ich. Für eine brauchbare Anwendung würde man z.B. die Eingabe des RE entsprechend aufbereiten, vielleicht verschiedene Bildbetrachter zur Auswahl stellen, auf jeden Fall verschiedene Bilder-Listen vorhalten und eventuell eine kleine Dateiverwaltung zum Umbenennen und Löschen von Bilderdateien anflanschen.
Vermutlich gibt es auch Units, die Bilder direkt anzeigen können, aber den Aufruf der bash mit beliebigen Inhalten direkt aus dem Programm heraus finde ich unschlagbar bequem und es mag Euch vielleicht zu weiteren Experimenten anregen, beispielsweise mit Musikdateien.

Viel Spaß!

TuxPeter
Zuletzt geändert von TuxPeter am 28.06.2022 22:21:30, insgesamt 2-mal geändert.

Benutzeravatar
tegula
Beiträge: 430
Registriert: 04.06.2004 13:51:04
Lizenz eigener Beiträge: MIT Lizenz

Re: RegEx Kurs: Pascal

Beitrag von tegula » 27.06.2022 12:26:45

Danke! Das Konzept komplett lauffähige Programme als Ausgangspunkt für die Aufgaben zu nutzen, gefällt mir sehr. Dadurch kann ich, als jemand der noch nie Kontakt zu Pascal hatte, trotzdem bei diesen Kurs-Part mitmachen :THX:.
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
2. Programm

NoPaste-Eintrag41756

Hier wurde das Beispiel in eine Prozedur verpackt. Die Matches werden weiterhin einzeln ausgegeben, allerdings jetzt ohne Zeilenvorschub, und zwecks besserer Übersicht außerdem durch einen Unterstrich getrennt. Erst am Ende gibt es einen Zeilenvorschub.

Es folgen im Hauptprogramm einige Aufrufe dieser Prozedur. Leider matcht die RE im Beispiel immer den ganzen zu durchsuchenden Text, Eure Aufgabe bestehen also darin, statt des All-Matchers ".*" die jeweils passende RE einzutragen.
Bei Aufgabe 2 hast du glaube ich vergessen, die Musterlösung vorher aus dem Beispielprogramm zu löschen (2. Programm, NoPaste-Eintrag41756, Zeile 26, Zeile 30, Zeile 34, Zeile 38, Zeile 42, Zeile 46, Zeile 50, Zeile 54).

Code: Alles auswählen

$ ./Pascal_prog\ zu\ RegEx_2 
Mitgeliefertes Beispiel, durchsuchter Text: "hello world! hello pascal!"
1. Mitgeliefertes Beispiel, RE: "hello (.*?)!"
world_pascal
2. Substrings (Reihung), Doppel-l ausgeben
hello world! hello pascal!_
ll_ll
3. Alternation von "lo" und "ld" ausgeben
hello world! hello pascal!_
lo_ld_lo
4. Quantoren, einfache und doppelte "l" ausgeben
hello world! hello pascal!_
ll_l_ll_l
5. Zeichenklasse: nur "l", "o", "a" ausgeben
hello world! hello pascal!_
l_l_o_o_l_l_l_o_a_a_l
6. Zeichenklasse mit Negation: "l", "o", "a" nicht ausgeben
hello world! hello pascal!_
h_e_ _w_r_d_!_ _h_e_ _p_s_c_!
7. Anker - 1. "hello" incl. 2 weiterer Zeichen ausgeben
hello world! hello pascal!_
hello w
8.  Esc. Zeichen - auszugeben ist: "[Hallöchen]"
hello world! [Hallöchen] hello pascal!_
[Hallöchen]

$ 

TuxPeter
Beiträge: 1762
Registriert: 19.11.2008 20:39:02
Lizenz eigener Beiträge: MIT Lizenz

Re: RegEx Kurs: Pascal

Beitrag von TuxPeter » 27.06.2022 14:32:17

Bei Aufgabe 2 hast du glaube ich vergessen, die Musterlösung vorher aus dem Beispielprogramm zu löschen
Danke für Deinen Beitrag, tegula, es sieht ganz so aus ... eine 2. Version ohne Lösungen gemacht und dann entschieden die falsche genommen ... keine Ahnung, ob ich das noch ändern kann, probiere es mal.

________ edit _____________

ja, ging, neu und jetzt hoffentlich wie geplant ohne meine Lösungen nach nopaste geschickt.

Huo
Beiträge: 401
Registriert: 26.11.2017 14:03:31

Re: RegEx Kurs: Pascal

Beitrag von Huo » 27.06.2022 23:28:41

Erst habe ich mit der Idee gefremdelt, mich mit einer neuen, mir bislang unvertrauten Programmiersprache zu befassen. Doch nach einigen Anlaufschwierigkeiten bin ich durchaus angefixt und weiß nicht, ob mich der pure Regexp-Aspekt oder Pascal mehr fesselt.
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
Wenn jemand mit diesem [2.] Programm weitere Aufgaben erarbeitet / ertestet - immer her damit!
Okay, ich habe mir eine Aufgabe gestellt, für deren Lösung ich gerade etwas länger gebraucht habe: Es sollen aus einem String wie "Paul McCartney, Jimi Hendrix, Paul Simon, Jim Morrison, Mick Jagger, John Lennon" die Nachnamen aller Personen ausgegeben werden, die mit Vornamen John oder Paul heißen (Ausgabe ohne Vornamen):

Code: Alles auswählen

writeln ('9. Nachnamen aller Personen mit Vornamen John oder Paul: "Paul McCartney, Jimi Hendrix, Paul Simon, Jim Morrison, Mick Jagger, John Lennon"');
ShowMatch ('(.*)', 'Paul McCartney, Jimi Hendrix, Paul Simon, Jim Morrison, Mick Jagger, John Lennon');
Tipp: Es funktioniert auch bei der Pascal RegEx-Unit ein Kniff, der im Python-Kurs kurz angesprochen wurde.

Und eine Zusatzaufgabe zur 7. Aufgabe ...

Code: Alles auswählen

writeln ('7. Anker - 1. "hello" incl. 2 weiterer Zeichen ausgeben');
ShowMatch ('(.*)', 'hello world! hello pascal!');
Finde eine Lösung ohne Anker (die z.B. auch funktioniert, wenn das erste "hello" nicht am Anfang der Textzeile steht). :mrgreen:

Außerdem habe ich eine etwas pingelige Pascal-Frage zum 3. Programm test_regex ...

Code: Alles auswählen

readln (txt, inStr);
find_and_write;
While not eof (txt) do
  begin
  readln (txt, inStr);
  find_and_write;
  end; // while
Kann man nicht die beiden Zeilen vor der While-Schleife ersatzlos streichen? Warum muss die erste Zeile der Text-Datei vor der Schleife ausgelesen und verarbeitet werden werden?

TuxPeter
Beiträge: 1762
Registriert: 19.11.2008 20:39:02
Lizenz eigener Beiträge: MIT Lizenz

Re: RegEx Kurs: Pascal

Beitrag von TuxPeter » 28.06.2022 07:55:38

Huo hat geschrieben: ↑ zum Beitrag ↑
27.06.2022 23:28:41
Kann man nicht die beiden Zeilen vor der While-Schleife ersatzlos streichen? Warum muss die erste Zeile der Text-Datei vor der Schleife ausgelesen und verarbeitet werden werden?
Hi,
danke für die Aufgaben - erst mal nur die Pascal-Frage: Damit es auch bei einer leeren Datei korrekt funktioniert, die bereits beim 1. Lesen EOF liefert. Aber ganz korrekt ist es trotzdem nicht, da hast du recht.
EDIT: Habe jetzt eine neuere und kürzere und vielleicht auch bessere Version des "test_regex" zu der ursprünglichen dazu gestellt. Grund für meine Irritation ist das unstrukturierte Gefummel, in das ich verfiel, als ich bemerkte, dass die Unit RegExpr einen Runtime Error produziert bei einem Leerstring als dem zu durchsuchendem String. Das sollte eigentlich nicht sein. Da sollte einfach kein Match stattfinden und fertig. Darum habe ich auch die Meldung von Leerzeilen herausgenommen.

Benutzeravatar
tegula
Beiträge: 430
Registriert: 04.06.2004 13:51:04
Lizenz eigener Beiträge: MIT Lizenz

Re: RegEx Kurs: Pascal

Beitrag von tegula » 29.06.2022 09:26:36

TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
Arbeitsvorschläge:

den RE so ändern, dass
1) alle "l" ausgeben werdem
2) nur das 2. "hello" finden und zusammen mit den nachfolgenden 2 Zeichen ausgeben (damit man sieht, dass es tatsächlich das 2. "hello" ist).
Quelltext: NoPaste-Eintrag41764
Ausgabe: NoPaste-Eintrag41765
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
3) Was ist der Unterschied in der Behandlung der Zeichen IN der inneren Klammer "()" und außerhalb dieser Klammer?
Nur die Zeichen innerhalb der Klammer werden extrahiert.
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
Weitere Arbeitsvorschläge:

- Finde alle Auslassungszeichen "'"

Code: Alles auswählen

$ cat muster_a3.txt
("|'|’|…|-|–|—|~)
$ ./'Pascal_prog zu RegEx_3' $(cat muster_a3.txt) schwaebische-kunde.txt
Ausgabe: NoPaste-Eintrag41767, Zeile 11-32
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
- alle Auslassungszeichen die einem b folgen, das b selber aber nicht.

Code: Alles auswählen

$ cat muster_a3.2.txt
b("|'|’|…|-|–|—|~)
$ ./'Pascal_prog zu RegEx_3' $(cat muster_a3.2.txt) schwaebische-kunde.txt
  2<Leerzeile>
  8 '
$ 
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
- jetzt mal etwas pfiffiger: Zeige alle Sätze, die den Buchstaben D enthalten (groß oder klein egal) bis genau zum ersten Vorkommen und einschließlich dieses Zeichens.

Code: Alles auswählen

$ ./'Pascal_prog zu RegEx_3' '(.*[Dd])' schwaebische-kunde.txt
Ausgabe: NoPaste-Eintrag41767, Zeile 44-92
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
- zeige alle Wörter einzeln unter Verwendung der Zeichenklasse \w, was dem [:alpha:] im egrep entspricht.

Code: Alles auswählen

$ ./'Pascal_prog zu RegEx_3' '\b(\w*)\b' schwaebische-kunde.txt
Ausgabe: NoPaste-Eintrag41767, Zeile 94-153
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
- zeige alle Sonderzeichen und vergleiche "^\w" mit "\W"

Code: Alles auswählen

$ ## ^\w
$ ./'Pascal_prog zu RegEx_3' '([^\w])' schwaebische-kunde.txt

Code: Alles auswählen

$ # \W
$ ./'Pascal_prog zu RegEx_3' '(\W)' schwaebische-kunde.txt
Ausgabe: (NoPaste-Eintrag41767, Zeile 155-276
Manuelle Schritte: Textdatei "klein_w.txt" erstellt und erste Ausgabe (NoPaste-Eintrag41767, Zeile 158-215) per Copy & Paste hineinkopiert. Anschließend Textdatei "groß_w.txt" erstellt und zweite Ausgabe ((NoPaste-Eintrag41767, Zeile 218-275) hineinkopiert.
Vergleich:

Code: Alles auswählen

$ diff -q klein_w.txt groß_w.txt
$ diff klein_w.txt groß_w.txt
$ 
Es scheint kein Unterschied zwischen der Zeichenklasse "[^\w]" und der Zeichenklasse "\W" zu bestehen.

Huo
Beiträge: 401
Registriert: 26.11.2017 14:03:31

Re: RegEx Kurs: Pascal

Beitrag von Huo » 29.06.2022 10:53:13

tegula hat geschrieben: ↑ zum Beitrag ↑
29.06.2022 09:26:36
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
- jetzt mal etwas pfiffiger: Zeige alle Sätze, die den Buchstaben D enthalten (groß oder klein egal) bis genau zum ersten Vorkommen und einschließlich dieses Zeichens.

Code: Alles auswählen

$ ./'Pascal_prog zu RegEx_3' '(.*[Dd])' schwaebische-kunde.txt
Ausgabe: NoPaste-Eintrag41767, Zeile 44-92
Diese Aufgabe fand ich sauschwer. 8O Zeigt Deine Lösung nicht die Zeilen bis zum letzten (statt wie gefordert ersten) Vorkommen des Buchstaben D/d? Auch unter Pascal verhalten sich reguläre Ausdrücke offenbar "gierig" ...

Huo
Beiträge: 401
Registriert: 26.11.2017 14:03:31

Re: RegEx Kurs: Pascal

Beitrag von Huo » 29.06.2022 16:29:43

TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
- zeige alle Wörter einzeln unter Verwendung der Zeichenklasse \w, was dem [:alpha:] im egrep entspricht.
Diese Aufgabe hat mich gerade besonders umgetrieben. :wink:

Meine Lösung:

Code: Alles auswählen

$ ./test_regex '(\w+)' schwaebische-kunde.txt
  1 Schwaebische_Kunde
  3 Als_Kaiser_Rothbart_lobesam
[...]
Da tegulas Lösung den Sternquantor benutzt ... "\b(\w*)\b" ..., gibt sie auch diverse leere Strings aus (nämlich für jeden Wortzwischenraum).

Wollte man diese Aufgabe mit egrep -o lösen, würde es übrigens keinen Unterschied machen, ob man den Plus- oder den Sternquantor verwendet, da egrep -o nur passende nicht-leere Teile ausgibt.

Für mich habe ich in der test_regex.pas eine entsprechende Anpassung vorgenommen, um die Anzeige leerer Matches zu unterbinden:

Code: Alles auswählen

while re.ExecNext do    // weitere Matches vorhanden ...
    if re.Match[1] <> '' then    // nur nicht-leere Matches ausgeben
        outS := outS + '_' + re.Match[1];    // U_Striche dazwischen und anhängen
Damit funktioniert auch tegulas Lösung ohne Ausgabe leerer Strings – wobei sogar die Wortgrenzenanker "\b" verzichtbar sind. :wink:

Benutzeravatar
tegula
Beiträge: 430
Registriert: 04.06.2004 13:51:04
Lizenz eigener Beiträge: MIT Lizenz

Re: RegEx Kurs: Pascal

Beitrag von tegula » 30.06.2022 16:13:49

Danke Huo für deine beiden Hinweise auf meine fehlerhaften Bearbeitung. Ich hab offensichtlich nicht aufmerksam genug überprüft, ob meine Ausgabe tatsächlich dem entspricht, was die Aufgabenstellung fordert :oops:
Huo hat geschrieben: ↑ zum Beitrag ↑
29.06.2022 16:29:43
Für mich habe ich in der test_regex.pas eine entsprechende Anpassung vorgenommen, um die Anzeige leerer Matches zu unterbinden:

Code: Alles auswählen

while re.ExecNext do    // weitere Matches vorhanden ...
    if re.Match[1] <> '' then    // nur nicht-leere Matches ausgeben
        outS := outS + '_' + re.Match[1];    // U_Striche dazwischen und anhängen
Damit funktioniert auch tegulas Lösung ohne Ausgabe leerer Strings – wobei sogar die Wortgrenzenanker "\b" verzichtbar sind. :wink:
Cool! Danke Dir :THX:
Huo hat geschrieben: ↑ zum Beitrag ↑
29.06.2022 10:53:13
tegula hat geschrieben: ↑ zum Beitrag ↑
29.06.2022 09:26:36
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
26.06.2022 11:15:29
- jetzt mal etwas pfiffiger: Zeige alle Sätze, die den Buchstaben D enthalten (groß oder klein egal) bis genau zum ersten Vorkommen und einschließlich dieses Zeichens.

Code: Alles auswählen

$ ./'Pascal_prog zu RegEx_3' '(.*[Dd])' schwaebische-kunde.txt
Ausgabe: NoPaste-Eintrag41767, Zeile 44-92
Diese Aufgabe fand ich sauschwer. 8O Zeigt Deine Lösung nicht die Zeilen bis zum letzten (statt wie gefordert ersten) Vorkommen des Buchstaben D/d? Auch unter Pascal verhalten sich reguläre Ausdrücke offenbar "gierig" ...
Ich glaube, ich hab jetzt eine Lösung mittels look-around (positive look-ahead) gefunden. Ich weiß aber nicht, wie ich das in Pascal formulieren kann. So sieht mein Lösungsvorschlag in R aus:

Code: Alles auswählen

library(tidyverse) # oder: library(magrittr)

# Textdatei (Schwäbische Kunde) importieren
schwaebische_kunde <- readr::read_lines(file = "schwaebische-kunde.txt") %>%
  dplyr::as_tibble() %>%
  dplyr::rename(text = value) %>%
  tibble::rowid_to_column("zeile")

# RE anwenden
## RE in Worten: Ist der erste Buchstabe ein d, so wird nur das d extrahiert [dD].
## Ist der erste Buchstabe hingegen kein d [^dD] , so werden alle Nicht-d-Zeichen _bis_ zum (ersten) d
## [^dD]*(?=[dD]) extrahiert _und_ (als letztes Zeichen) wird noch ebendieses d [dD] extrahiert.
schwaebische_kunde$bis_zum_ersten_d <- schwaebische_kunde$text %>%
  stringr::str_extract("[dD]|[^dD]*(?=[dD])[dD]")

# Ergebnis ausgeben
print(x = schwaebische_kunde,
      n = Inf)
Folgende Ausgaben kommt dabei heraus: NoPaste-Eintrag41773 --> Die Ausgabe scheint die Aufgabenstellung zu erfüllen :).

EDIT (30.6.2022, 16:30): Formulierung der RE in Worten verbessert.

Huo
Beiträge: 401
Registriert: 26.11.2017 14:03:31

Re: RegEx Kurs: Pascal

Beitrag von Huo » 30.06.2022 18:43:36

tegula hat geschrieben: ↑ zum Beitrag ↑
30.06.2022 16:13:49
Ich glaube, ich hab jetzt eine Lösung mittels look-around (positive look-ahead) gefunden. Ich weiß aber nicht, wie ich das in Pascal formulieren kann.
Von diesen Lookarounds, die ich noch nie benutzt habe, habe ich nur eine sehr vage Vorstellung. :oops: Doch als ich mir deine Lösung für R genauer anschaute, kam mir die Erleuchtung, wie man mit der Grundidee auch ohne Lookaround zum Ziel gelangt. Echte Teamarbeit also ... :THX:

Code: Alles auswählen

$ ./test_regex "(^[^Dd]*[Dd])" schwaebische-kunde.txt
  1 Schwaebische Kund
  4 Zum heil'gen Land
  5 D
  6 D
  7 D
  8 Viel Steine gab's und
  9 Und
 10 Hat d
[...]
Erläuterung: Erst wird mit "^" der Zeilenanfang verankert. Danach dürfen beliebig viele (evtl. auch keine) Nicht-Ds vorkommen ("[^Dd]*") – bis zum ersten Auftreten eines D ("[Dd]").

Im Grunde lag Meillos Aufgabe 9 in RegExp-Kurs 06 das gleiche Prinzip zugrunde, wie ich mich jetzt erinnere. Wir hätten hier also auch schneller auf die Lösung kommen können. :wink:

Benutzeravatar
tegula
Beiträge: 430
Registriert: 04.06.2004 13:51:04
Lizenz eigener Beiträge: MIT Lizenz

Re: RegEx Kurs: Pascal

Beitrag von tegula » 30.06.2022 19:47:30

Klasse :THX:.

TuxPeter
Beiträge: 1762
Registriert: 19.11.2008 20:39:02
Lizenz eigener Beiträge: MIT Lizenz

Re: RegEx Kurs: Pascal

Beitrag von TuxPeter » 01.07.2022 11:20:54

Hallo allerseits,

zunächst mal vielen Dank für Eure Beiträge.

Diese Lösung für die Aufgabe "... bis zum 1. d oder D" entspricht genau meiner Lösung.

Code: Alles auswählen

./test_regex "(^[^Dd]*[Dd])" schwaebische-kunde.txt
mit egrep kommt übrigens das Gleiche heraus:

Code: Alles auswählen

egrep -no "^[^D|d]*[D|d]" schwaebische-kunde.txt
Die übrigen meiner Aufgaben sind ja ziemlich trivial, und Ihr habt soweit ich sehe die gleichen oder bessere Lösungen und auch noch bessere Aufgaben gefunden.

Was ist der Unterschied in der Behandlung der Zeichen IN der inneren Klammer "()" und außerhalb dieser Klammer?
Die Zeichen außerhalb der Klammer werden von "re(Match[1])" nicht ausgeben.
Für das reine Auffinden der Matches sind beispielsweise folgende Ausdrücke identisch:

Code: Alles auswählen

rn "(aber war.+)" "es war nicht oder aber war doch"
aber war doch
rn "aber( war.+)" "es war nicht oder aber war doch"
 war doch

(Anmerkung: rn ist ein Alias für den Progr.aufruf)

Ich habe noch zwei ganz kleine, ganz minimalistische Pas-Progs. geschrieben, das erste schaut nur nach, ob überhaupt ein Match vorhanden ist und zeigt diesen, das zweite zeigt alle Matches, wieder mit den U_Strichen dazwischen. Ich verwende hier die Kommandozeilen-Parameter direkt als Funktionsparameter, auf einen Read-Befehl im Programm oder irgendewelche Fehlerbehandlung oder Plaus-Tests wird verzichtet. Hier mal die beiden Programme einschließlich der verwendeten Aliases, alles aus Übersichtlicheitsgründen in einem NoPaste.

NoPaste-Eintrag41774

ich habe ein bisschen rumgefummelt und gefunden, dass damit sogar ganze Dateien der Pascal'schen RE-Engine vorgelegt werden können, - mithilfe der Back-Tickes zur vorrangigen Auflösung duchr die bash - allerdings nur bis zu einer bestimmten Größe, ich möchte wetten, es sind 255 Zeichen.

Code: Alles auswählen

rn "(\w+)" "`cat -v schwaebische-kunde.txt`"
Schwaebische_Kunde_Als_Kaiser_Rothbart_lobesam_Zum_heil_gen_Land_gezogen_kam_Da_musst_er_mit_dem_frommen_Heer_Durch_ein_Gebirge_wuest_und_leer_Daselbst_erhub_sich_grosse_Noth_Viel_Steine_gab_s_und_wenig_Brot_Und_mancher_deutsche_Reitersmann_Hat_do

Huo
Beiträge: 401
Registriert: 26.11.2017 14:03:31

Re: RegEx Kurs: Pascal

Beitrag von Huo » 01.07.2022 19:09:06

Vielen Dank, dieser Kursteil bot einen anregenden Einblick in Pascal, dem ich auch als Amateur-Programmierer gut folgen konnte. :THX:
TuxPeter hat geschrieben: ↑ zum Beitrag ↑
01.07.2022 11:20:54
Was ist der Unterschied in der Behandlung der Zeichen IN der inneren Klammer "()" und außerhalb dieser Klammer?
Die Zeichen außerhalb der Klammer werden von "re(Match[1])" nicht ausgeben.
Ich habe mich gefragt, wie man bei mehreren geklammerten Unterausdrücken im Regex alle ausgeben kann. Mit re.Match[1] berücksichtigen deine Programme ja offensichtlich nur jeweils den ersten (in deinen Beispielen auch einzigen) geklammerten Unterausdruck. Nach ein bisschen Tüftelei konnte ich dein Programm test_regex_a.pas entsprechend erweitern:

Code: Alles auswählen

program test_regex_a1;
uses RegExpr;
var  re: TRegExpr; i: longint;
    begin
    re := TRegExpr.Create (ParamStr(1));  // 1. Param als Regex übergeben
    if re.Exec (ParamStr(2)) then         // 2. Param als durchsuchter String
        for i := 1 to re.SubExprMatchCount do   // Iteration über alle geklammerten Unterausdrücke
        begin
            WriteLn (re.Match[i]);   // Ausgabe 1. Treffer zum i.ten Unterausdruck
        end;
re.Free;
end.
Am schwierigsten war es, in der Dokumentation die passende Funktion SubExprMatchCount für die Anzahl der Unterausdrücke zu finden. :wink:

Anwendungsbeispiel:

Code: Alles auswählen

$ ./test_regex_a1 "(nicht)? (o.*(aber|jedoch)) (war.+)" "es war nicht oder aber war doch"
nicht
oder aber
aber
war doch

TuxPeter
Beiträge: 1762
Registriert: 19.11.2008 20:39:02
Lizenz eigener Beiträge: MIT Lizenz

Re: RegEx Kurs: Pascal

Beitrag von TuxPeter » 01.07.2022 21:00:59

Toll!
Ich hatte mich natürlich schon gefragt warum ...re.Match[1] inidiziert ist bzw. was sich hinter den folgenden indizierten Elementen verbirgt. War mir aber zu weit weg von meinen anderen Baustellen, um dem nachzugehen - jetzt weiß ich es!

Antworten