RegEx - Suche nach Jahreszahlen liefert nur 2stelliges Ergebnis

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

Guten Abend,

da ich mich derzeit mit RegEx-Gruppierung beschäftige wollte ich Foglendes lösen. Aus einem String Jahreszahlen aus den 1900ern und 2000ern heraussuchen. Ich sage also meinem Suchmuster, dass es nach 19 oder 20 mit 2 Zahlen anschließend suchen soll. Hier mein Code:

Code: Alles auswählen

import re
pattern = '(19|20)\d{2}'
string = '2014, 3143, 1923, 3143'
print(re.findall(pattern, string))
Als Ergebnis erhalte ich

Code: Alles auswählen

['20', '19']
Eigentlich habe ich mit ['2014', '1923'] gerechnet. Wo liegt den mein Fehler und wie schaffe ich es auf mein gewünschtes Ergebnis?
Danke schon jetzt für eure Hilfe.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@luvlinux: Du bildest eine Gruppe nur über die ersten beiden Ziffern. Lösung: die Gruppe über alle Ziffern, oder gar keine Gruppe. Dein Ausdruck ist aber in sofern falsch, dass auch aus 31983 oder 20134 die vier Ziffern gefunden werden.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Anstelle des regulären Ausdrucks lassen sich auch Python-Boardmittel verwenden:

Code: Alles auswählen

text = '2014, 3143, 1923, 3143'
for year in map(int, text.split(',')):
    if 1900 <= year < 2100:
        print(year)
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

Funktioniert aber nur, wenn es ein schöner String ist. In anderen Fällen ("Im Jahr 1996..."), also wenn der String auch Wörter enthält, funktioniert es nicht mehr.

Ich habe mal folgendes versucht und das Tool eingesetzt:

Code: Alles auswählen

>>> string = "Im Jahr 1996 wird das 2010-Programm aufgesetzt. Es führt zu 3045 Abfragen und 11945 Ergebnissen."
>>> re.findall("\d+", string)
['1996', '2010', '3045', '11945']
>>> re.findall("19\d{2}|20\d{2}", string)
['1996', '2010', '1945']
Ich würde - falls dein Problem komplex ist, wie ich es vermute - mittels re.findall("\d+", string) alle Zahlen suchen lassen und da - meine zweite Variante auch viele Falsch-Positive liefert (11945 -> 1945 :( ) - dann mit dem Vorschlag von snafu filtern. So könnte es gehen.

Vielleicht hat noch jemand eine Idee...
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

pixewakb hat geschrieben:Ich würde - falls dein Problem komplex ist, wie ich es vermute - mittels re.findall("\d+", string) alle Zahlen suchen lassen und da - meine zweite Variante auch viele Falsch-Positive liefert (11945 -> 1945 :( ) - dann mit dem Vorschlag von snafu filtern.
Diesen Vorschlag unterstütze auch ich. Reguläre Ausdrücke sind ganz gut, um Tokens zu finden (z.B. Wörter ohne die Satzzeichen oder Zahlen mit einer oder mehreren Ziffern). Die genauere Bedeutung solcher Fundstücke (hier: Bereich, in dem die Zahl liegt) prüft man besser mit den Möglichkeiten der Programmiersprache. Denn da stoßen reguläre Ausdrücke an ihre Grenzen: Es wird kompliziert bis unmöglich, damit alle Fälle abzudecken.
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

Hallo zusammen,

vielen Dank für eure ausführlichen Antworten.
Ein Zusammenspiel zwischen den regex und Python-Programmierung ist wohl der beste Lösungsansatz. Ich werde mit diese Kombination mal noch etwas experimentieren.

Wünsche euch allen noch einen guten Rutsch und bis bald.

Jonathan
ruedi_br
User
Beiträge: 13
Registriert: Montag 11. September 2017, 08:24

Im Beispielcode von pixewakb würde ich anstelle von "\d+" nach "\d\d\d\d" suchen, dann sollte 11945 herausfallen. lt. Hilfe sollte auch \d{4} tun, habe ich aber nicht probiert :|
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

M. E. nein:

Code: Alles auswählen

>>> import re
>>> string = "Im Jahr 1996 wird das 2010-Programm aufgesetzt. Es führt zu 3045 Abfragen und 11945 Ergebnissen."
>>> re.findall("\d{4}", string)
['1996', '2010', '3045', '1194']
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Kann man reparieren:

Code: Alles auswählen

re.findall("(?:[^\d]|^)(\d{4})(?:[^\d]|$)", string)
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

@__deets__

Das klappt ja super. Ich versuche jetzt mal deine regex zu verstehen:
Du suchst also zuerst nach einem Nicht-digit [^\d]. Damit dieses nicht als Ergebnis erfasst wird, verwendest du ?:
Dann nach 4 digits hintereinander \d{4} anschließend wieder nach einem Nicht-digit, der wiederum mit ?: vom Ergebnis ausgeschlossen wird.

Liege ich soweit richtig? Bzw. was ich aber nicht verstehe ist das erste Oder ( ...|^) und das zweite Oder ( ...| $). Kannst du mir hier nochmals auf die Sprünge helfen?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

So weit richtig. Allerdings würde ohne ^ bzw $ eine Zahl ganz am Anfang bzw Ende nicht gefunden werden. Genau diese Fälle decken die veroderungen ab.
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

Oki doki,

dann scheine ich es ja jetzt gerafft zu haben :D

Thx nochmals.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Was spricht denn gegen dies?

Code: Alles auswählen

numbers = map(int, re.findall(r'\d+', text))
years = [year for year in numbers if 1900 <= year < 2100]
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@__deets__: das Gegenteil von »\d« ist »\D«:

Code: Alles auswählen

re.findall(r"(?:\D|^)(\d{4})(?:\D|$)", string)
Klarer was gemeint ist, wird es durch negativen look behind und look ahead:

Code: Alles auswählen

re.findall(r"(?<!\d)\d{4}(?!\d)", string)
kann man jetzt natürlich noch mit der Jahrhundertsuche verknüpfen:

Code: Alles auswählen

re.findall(r"(?<!\d)(?:19|20)\d{2}(?!\d)", string)
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

@snafu Es spricht nix „dagegen“. Es löst ja das Problem. Wenn man aber eh schon rexe drin hat, dann halt richtig. Sonst mach einfach split & int(), und fang den ValueError ab.
Antworten