Du kennst schon die Grundlagen: Zeichenklassen wie \d und [A-Z],
Quantifizierer wie {4} und + und die Wörter matches?,
all-matching-subseqs und re-replace. Diese Übung ergänzt die Teile
regulärer Ausdrücke, die Struktur und Kontext beschreiben. Sie
stecken im selben Vokabular regexp, geschrieben mit
demselben Literal R/ /.
Anker finden eine Position statt eines Zeichens:
^ ! the start of the string
$ ! the end of the string
\b ! a word boundary
Weil $ das Ende des Strings bedeutet, schreibst du \$, wenn du ein
wörtliches Dollarzeichen meinst.
Eine Wortgrenze (\b) ist die Stelle, an der ein Wortzeichen auf
ein Nicht-Wortzeichen trifft. Wortzeichen sind Buchstaben, Ziffern und
der Unterstrich; alles andere (Leerzeichen, Satzzeichen und die beiden
Enden des Strings) gilt als Grenze. \bcat\b findet cat also nur,
wenn es allein steht:
USING: regexp ;
"cat nap" R/ \bcat\b/ re-contains? . ! => t
"scatter" R/ \bcat\b/ re-contains? . ! => f (cat sits inside a word)
Runde Klammern ( ) fassen einen Teil eines Musters zu einer Gruppe
zusammen, damit ein Quantifizierer oder ein | („dieses oder
jenes“) für die ganze Gruppe gilt:
USING: regexp ;
"abab" R/ (ab)+/ matches? . ! => t (+ applies to "ab")
"dog" R/ cat|dog/ matches? . ! => t (either word)
"$100.50" R/ \$\d+(\.\d{2})?/ matches? . ! => t
"$100" R/ \$\d+(\.\d{2})?/ matches? . ! => t (the group is optional)
Das letzte Muster liest sich als „ein Dollarzeichen, eine oder mehrere Ziffern, dann eine optionale Gruppe aus einem Punkt und zwei Ziffern“.
Hinweis: In Factor strukturiert eine Gruppe das Muster nur. Anders als bei manchen Regex-Werkzeugen kannst du den Text, den eine Gruppe gefunden hat, nicht wieder herausziehen: Es gibt keine Extraktion von Capture-Gruppen, und Backreferences wie
\1werden nicht unterstützt. Um Text um einen Bezugspunkt herum herauszupicken, nutze stattdessen Lookaround (siehe unten).
Ein Lookaround prüft, was direkt vor oder direkt nach der aktuellen Stelle kommt, ohne diese Zeichen zum Treffer zu machen. Er ist „nullbreit“: Was er prüft, ist nicht Teil des Ergebnisses.
(?=...) ! lookahead: ... must follow
(?!...) ! negative lookahead: ... must NOT follow
(?<=...) ! lookbehind: ... must come before
(?<!...) ! negative lookbehind: ... must NOT come before
Lookaround ist selektiv: Dieselben Zeichen passen oder passen nicht,
je nachdem, was daneben steht. Ein einfaches \d+ schnappt sich jede
Zahl; Lookbehind und Lookahead behalten nur die mit dem passenden
Nachbarn:
USING: regexp ;
"3 for $10 or 10 for $30" R/ \d+/ all-matching-subseqs .
! => { "3" "10" "10" "30" } (every number)
"3 for $10 or 10 for $30" R/ (?<=\$)\d+/ all-matching-subseqs .
! => { "10" "30" } (only the prices — the bare 3 and 10 drop)
"buy 2 at 15% off, 4 at 30% off" R/ \d+(?=%)/ all-matching-subseqs .
! => { "15" "30" } (only the discounts — the bare 2 and 4 drop)
Füge einen Buchstaben nach dem schließenden / an, um zu ändern, wie
sich das ganze Muster verhält. i macht es unabhängig von Groß- und
Kleinschreibung:
USING: regexp ;
"REFUND" R/ refund/i matches? . ! => t
"Refund" R/ refund/i matches? . ! => t
all-matching-subseqs liefert dir den gefundenen Text. map-matches
geht einen Schritt weiter: Es führt für jeden Treffer eine Quotation
aus und sammelt die Ergebnisse. Die Quotation bekommt den
start-Index des Treffers, seinen end-Index und den ganzen String;
subseq macht aus diesen dreien den gefundenen Text, den du dann
umwandelst:
map-matches ( string regexp quot: ( start end string -- obj ) -- seq )
USING: math regexp sequences ;
"a1 b22 c333" R/ \d+/ [ subseq length ] map-matches .
! => { 1 2 3 } (the length of each run of digits)
Sein nur auf Nebenwirkungen ausgelegter Verwandter,
each-match, führt für jeden Treffer eine Quotation aus,
ohne etwas zu sammeln.
Du führst die Bücher für ein kleines Unternehmen, und jeden Tag kommen die Transaktionen als unordentliche Textzeilen herein. Du verwendest reguläre Ausdrücke, um Beträge zu validieren, Zahlen anhand des Symbols herauszuziehen, das sie kennzeichnet, und Zeilen zu markieren, die genauer angesehen werden müssen.
Ein wohlgeformter Betrag besteht aus einem $, einer oder mehreren Ziffern und einem optionalen Cent-Teil aus einem Punkt gefolgt von genau zwei Ziffern: $100 oder $100.50, aber nicht $100.5. Definiere valid-amount?, das t zurückgibt, wenn der ganze String ein wohlgeformter Betrag ist.
"$100.50" valid-amount? . ! => t
"$100" valid-amount? . ! => t
"$100.5" valid-amount? . ! => f
Definiere dollar-amounts, das jede Zahl zurückgibt, die in der Zeile direkt auf ein $ folgt, nur die Ziffern, ohne das $, und zwar in der Reihenfolge ihres Auftretens.
"spent $100 and $25 today" dollar-amounts .
! => { "100" "25" }
Definiere percentages, das jede Zahl zurückgibt, auf die unmittelbar ein % folgt.
"up 5% then down 12%" percentages .
! => { "5" "12" }
Definiere flagged?, das t zurückgibt, wenn die Zeile refund oder chargeback in beliebiger Groß- und Kleinschreibung erwähnt.
"Issued a REFUND today" flagged? . ! => t
"a normal sale" flagged? . ! => f
Melde dich bei Exercism an, um Factor mit 47 Konzepte163 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.