String in Text suchen und Satz ausgeben

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.
harvey186
User
Beiträge: 20
Registriert: Mittwoch 14. Dezember 2016, 08:20

tja, dann muss mein Sohn wohl damit leben, dass immer nur das Wort plus Rest des Satzes bekommt. Bzw. wie mir bei StackmOverflow gezeigt wurde, dass ein paar Sätze ausgegeben werden, die das Suchwort nicht beinhalten. Wobei mir das total nklar ist, warum python das macht.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nein, du solltest deinen Ansatz überdenken: Kein regulärer Ausdruck, der mit ``re.findall()`` die gesuchten Sätze isoliert, sondern besser definieren, wo ein Satz getrennt werden soll. Das habe ich ja ansatzweise mit meinem Codeschnipsel gezeigt. Anschließend kann man mit Python-Boardmitteln (``if wort in satz``) die Treffer rausfischen.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mein Ansatz erweitert mit Filterfunktionalität:

Code: Alles auswählen

import re

SENTENCE_SPLITTER = re.compile(r"(?<=[.!?]) ").split

TEXT = (
"Der Hase liegt im Gras. Das Kind schaut ihm dabei zu. Aber plötzlich "
"passiert es: Der Hase hoppelt davon!")

def find_sentences(text, needle, splitter=SENTENCE_SPLITTER):
    # return [sentence for sentence in splitter(text) if needle in sentence]
    matches = []
    for sentence in splitter(text):
        if needle in sentence:
            matches.append(sentence)
    return matches

def main():
    print("Beispieltext:")
    print(TEXT)
    rabbit_sentences = find_sentences(TEXT, "Hase")
    print("\nSätze mit 'Hase':")
    print("\n".join(rabbit_sentences))
    child_sentences = find_sentences(TEXT, "Kind")
    print("\nSätze mit 'Kind':")
    print("\n".join(child_sentences))
     
if __name__ == "__main__":
    main()
Zuletzt geändert von snafu am Donnerstag 15. Dezember 2016, 19:35, insgesamt 1-mal geändert.
BlackJack

@harvey186: Python macht da gar nichts, ausser genau das was man sagt. Wenn das etwas anderes ist als man will, dann hat man sich nicht richtig ausgedrückt. Und Dein Sohn muss damit nur leben wenn Du es nicht besser hinbekommst, was nicht an Python liegt, denn das es grundsätzlich geht, habe ich mit dem C#-Programm ja gezeigt. Das kann man auch in Python, oder jeder anderen Programmiersprache so umsetzen, die die Grundlegenden Datentypen und Operationen verwendet, die dort auch verwendet werden. Man kommt sogar mit weniger abstrakten Konstrukten aus, zum Beispiel Listen statt eine Methode mit ``yield return`` zum Iterator zu machen, was nicht in jeder Sprache möglich ist. (In Python schon.)

@All: FYI: print complete line which includes a search word
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Stellt sich die Frage, warum der Papa die Hausaufgabe des Sohnes lösen soll und dies offenbar auch nur im Schnellverfahren lösen will (oder muss). Da wird fast nichts hängenbleiben und so wird man in der Ausbildung (oder was auch immer es ist) auf Dauer nicht durchkommen. Aber muss ja jeder selber wissen...
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Auch das Finden eines Wortes kann man relativ simpel mit regulären Ausdrücken verbessern, damit z.B. die Suche nach "Baum" nicht auch "Baumschule" und "Baumhaus" zurückliefert:

Code: Alles auswählen

import re

def has_word(text, word):
    return word in re.findall(r"\w+", text)

def main():
    print(has_word("Ich gehe ins Baumhaus.", "Baum"))
    print(has_word("Er pflanzt einen Baum.", "Baum"))
    print(has_word("Sie fuhr zur Baumschule.", "Baum"))

if __name__ == "__main__":
    main()
Auch dies ist sicherlich nicht perfekt, deckt aber schon einiges ab.
harvey186
User
Beiträge: 20
Registriert: Mittwoch 14. Dezember 2016, 08:20

@snafu, nee, nix hausaugaben oder so. Es ist für nen Twitterbot und er wollte Hilfe von mir, weil ich programmier Erfahrungen habe (erst seit 4 Wochen mitPython) und schon ein paar bots programmiert habe

@blackjack, mal sehen auf welchen Python Foren wir noch zusammen treffen ;)

Ichhab morgen ja einiges von hier zum "spielen", danke
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Am wichtigsten beim Verarbeiten von Texten: Überlege dir gut, ob du reguläre Ausdrücke brauchst oder ob das nicht auch relativ trivial mit den Mitteln von Python möglich ist. Python kann da viel mehr als man anfangs denkt. Dazu solltest du Grundkenntnisse über die Möglichkeiten von regulären Ausdrücken haben, um das abschätzen zu können. Mache dich auch mit den ganzen Backslash-Dingern vertraut, da sie bei richtigem Einsatz die Lesbarkeit der Regex stark verbessern können. Zurückgucken und Vorwärtsgucken gibt es auch und das ist manchmal vorteilhafter anstatt mit Gruppen zu arbeiten. Generell auch lieber 2-3 Einzelschritte in Kombination mit Python-Methoden gehen anstatt alles mit einer Regex zu erschlagen. Reguläre Ausdrücke repräsentieren Muster: Überlege dir ob das zu findende Muster einfach ist (dann `search()` / `match()` oder `findall()`) oder ob es leichter ist, ein Muster zum Trennen anzuwenden (dann `split()`).

Ansonsten noch der Hinweis, dass es spezielle Libs zum Verarbeiten von Sprache gibt: NLTK oder PyParsing wären da so Beispiele mit verschiedenen Zielsetzungen. Da muss man aber schauen ob einem das wirklich was bringt. NLTK ist eher sprachwissenschaftlich ausgelegt. PyParsing macht viele Dinge, die IMHO auch leicht mit dem `re`-Modul von Python machbar sind.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Iterativ:

Code: Alles auswählen

# from itertools import ifilter, takewhile, count
# import io

In [125]: s = 'Im Jahr 1895 wird einem jungen Einwandererpaar auf Ellis Island die Einreise in die USA verweigert, weil beide an der Tuberkulose erkrankt sind. Als ihrem kleinen Sohn auch ohne sie die Einreise verweigert wird, beschlie\xc3\x9fen sie, ihn in ein Modellsegelboot mit dem Namen Stadt der Gerechtigkeit zu setzen, in der Hoffnung, das Kind werde gefunden.\n1916 ist das Kind zu einem Mann herangewachsen. Er nennt sich Peter Lake. Peter wurde von dem Gangsterboss Pearly Soames aufgezogen und verdient sich seinen Lebensunterhalt nun als Einbrecher und Dieb. Als Peter beschlie\xc3\x9ft, Pearlys Bande und sein altes Leben zu verlassen, macht er sich seinen Ziehvater zum w\xc3\xbctenden Gegner und wird von dessen Bande gejagt.'.decode('utf-8')

In [126]: sio = io.StringIO(s)

In [128]: ifilter(lambda s: 'Kind' in s, takewhile(lambda x: sio.tell() < len(s), (''.join(takewhile(lambda c: c not in ('.', '?', '!'), character_iterator)).strip() for _ in count(0))))
Out[128]: <itertools.ifilter at 0x6fffeb90610>

In [129]: list(_)
Out[129]:
[u'Als ihrem kleinen Sohn auch ohne sie die Einreise verweigert wird, beschlie\xdfen sie, ihn in ein Modellsegelboot mit dem Namen Stadt der Gerechtigkeit zu setzen, in der Hoffnung, das Kind werde gefunden',
 u'1916 ist das Kind zu einem Mann herangewachsen']
Das Ganze in bisschen verständlicher:

Code: Alles auswählen

In [146]: character_iterator = iter(partial(sio.read, 1), '')

In [147]: single_sentence_iterator = takewhile(lambda c: c not in ('.', '?', '!'), character_iterator)

In [148]: all_sentences_iterator = (''.join(takewhile(lambda c: c not in ('.', '?', '!'), character_iterator)).strip() for _ in count(0))

In [149]: sentences_until_end_iterator = takewhile(lambda x: sio.tell() < len(s), all_sentences_iterator)

In [150]: only_relevant_sentences_iterator = ifilter(lambda s: 'Kind' in s, sentences_until_end_iterator)

In [151]: list(only_relevant_sentences_iterator)
[u'Als ihrem kleinen Sohn auch ohne sie die Einreise verweigert wird, beschlie\xdfen sie, ihn in ein Modellsegelboot mit dem Namen Stadt der Gerechtigkeit zu setzen, in der Hoffnung, das Kind werde gefunden',
 u'1916 ist das Kind zu einem Mann herangewachsen']
Leider weiß ich nicht besser wie ich das tatsächliche Ende erkennen soll, `takewhile(lambda x: sio.tell() < len(s), all_sentences_iterator)` kommt mir unnötig vor, ist außerdem ungut, da ich die komplette Größe des Files kennen muss (via `seek` ans Ende und `tell` jetzt nicht so wild, trotzdem blöd). Der `all_sentences_iterator` müsste wissen wann der `character_iterator` erschöpft ist und dann beenden (has_next!?).

Natürlich kann man das auch in for-Schleifen (mit weniger Iteratoren) lösen, aber das sei dem OP überlassen.
the more they change the more they stay the same
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Finde ich übermäßig kompliziert und es ist allein schon aufgrund der ganzen Lambdas sicherlich nicht gerade performant. Zum Ausprobieren ganz nett, aber einen ernsthaften Einsatz würde ich hier nicht empfehlen.
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

BlackJack hat geschrieben:Hm, das mit dem anpassen des regulären Ausdrucks nehme ich zurück. Ich hab's nicht hinbekommen den einfach zu erweitern, so das wirklich alle Fälle abgedeckt sind und das trotzdem noch kompakt und lesbar bleibt.
Irgendwie bin ich blind, warum ein simpler regulärer Ausdruck nicht funktionieren sollte. Hast Du grad einen problematischen Beispielsatz zur Hand?
BlackJack

@Sirius3: Naja der Beispieltext. Das sind ja mehrere Sätze. Vielleicht stelle ich mich auch nur zu blöd an *einen* regulären Ausdruck zu schreiben der jeden Satz da rausfischt in dem irgendwo das Wort frei stehend vorkommt. Vielleicht sträubt sich da aber auch irgendwas in mir dagegen mit einem Ausdruck komplizierter zu denken als eine IMHO verständlichere Lösung die das in zwei Teile aufsplittet.
BlackJack

Lösung in Io mit regulären Ausdrücken aber mit der Aufteilung in die beiden Schritte in Zeilen aufteilen, und dann in jeder Zeile zu suchen:
[codebox=io file=Unbenannt.txt]#!/usr/bin/env io

Regex


load := method(filename,
file := File clone openForReading(filename)
contents := file contents
file close
contents
)


Sequence containsWord := method(word,
pattern := "(^|\\b)#{word escapeRegexChars}(\\b|$)" interpolate
self findRegex(pattern) isNil not
)


Sequence asSentences := method(
self allMatchesOfRegex("\\s*(.*?\\.)") map(at(1))
)


main := method(
load("tale.txt") asSentences select(containsWord("Luzifer")) foreach(println)
)


isLaunchScript ifTrue(main)[/code]
Bei Io finde ich ja immer witzig das man es teilweise fast wie normale Sätze lesen kann, es auf der anderen Seite aber auch oft nach Meister Yoda klingt. :-)
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

Ich hab da auch etwas Senf:

Ich kann nur empfehlen nicht mit einen Regex-Ausdruck über den kompletten Text zu gehen, sondern das Problem vorher zu Partitionieren.
Komplexe Reguläre-Ausdrücke können schnell mal im Aufwand explodieren. In der Regel wird das aber nur kritisch wenn man mit einen suboptimalen Ausdruck viele MB z.B. Logfiles durchsuchen will.

Mein vorgehen:
* Eine Liste<str> wird zu einen Textblock zusammengejoint. ( Absätze sind nicht Teil des Problems)
* Den Textblock danach an den Satzzeichen [.?!] wieder in eine Liste aus Sätzen und Satzzeichen auftrennen.
* Jedes zweite Element (Satz) wird mit seinen Nachfolger (Satzeichen) wieder vereint.
* Entferne in jedem Satz Bindestriche mit umgebenden Leerzeichen und Zeilenumbrüchen so das getrennte Wörter wieder zusammen gezogen werden. (Die Lösung ist noch nicht so optimal)
* Entferne am Ende und am Anfang jedes Satzes Leerzeichen.
* Filter die Sätze nach dem vorkommen des gesuchten Wortes.
  • * Gefunden wird das Wort wenn ein Leerzeichen oder Komma vorangeht und am Satzende steht bzw. ein Leerzeichen oder Komma folgt,
    * Gefunden wird das Wort wenn es am Satzanfang steht und ein Leerzeichen oder Komma folgt bzw. das Wort am Satzende steht

Code: Alles auswählen

# -*- coding: utf-8 -*-
import re

def suche(text, wort, case_sensitive=False):

    re_flags = re.IGNORECASE if case_sensitive else 0

    # Wenn der Text als Liste daher kommt, joine die Elemente einer Liste zu einen Text zusammen
    if isinstance(text,list):
        text = " ".join(text)

    try:
        text = text.decode('utf-8')
    except UnicodeEncodeError:
        pass

    try:
        wort = wort.decode('utf-8')
    except UnicodeEncodeError:
        pass

    # Gefunden wird das Wort wenn ein Leerzeichen vorangeht und am Satzende steht bzw ein Leerzeichen oder Komma folgt,
    # Gefunden wird das Wort wenn es am Satzanfang steht und ein Leerzeichen oder Komma folgt bzw. das Wort am Satzende steht.
    WORT_RE = re.compile(u'((?<=^)|(?<=[\s\,]))%s(?=[\s\,]|$)' % wort)

    # Ausdruck zum erfassen von Bindestrichen incl. aller Leerzeichen und Zeilenumbrüche vor und nach dem Bindestrich
    BINDESTRICH_RE = re.compile(u'[\s\n]*-[\n\s]')

    # Zerlege den Text in eine Liste von Textblöcken und Satzzeichen
    splitter = re.split(u'([\.\!\?])', text)

    # Entferne die Bindestriche Zeilenumbrüche und führende und endständige Leerzeichen im Satz.
    # Füge jeden zweiten Textblock mit dem folgenden Satzzeichen zusammen
    saetze = ( BINDESTRICH_RE.sub(u'',  a ).replace('\n',' ').strip(' ')  + b for (a,b) in zip(splitter[::2], splitter[1::2]) )

    # Gib eine Liste mit den gefunden Sätzen zurück.
    return list(satz for satz in saetze if WORT_RE.search( satz , re_flags ) )

Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

@pyHoax: meinst Du mit Aufwand Rechenzeit? Diese Überlegung sollte man erst machen, wenn es tatsächlich ein Problem wird. Funktionen sollten keine allmächtiges Interface haben. Wenn suche einen Unicodestring als text erwartet ist es die Aufgabe des Aufrufers aus seinen Daten einen zu erzeugen (der kann auch ein ' '.join ausführen). In regulären Ausdrücken gibt es \b für word-Boundary, das genau das macht, was Du da versuchst, kompliziert mit look-behind nachzubilden. wort sollte mit re.escape escaped werden. Dieses Bindestrichentfernen verstehe ich nicht ganz.
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

meinst Du mit Aufwand Rechenzeit? Diese Überlegung sollte man erst machen, wenn es tatsächlich ein Problem wird.
Dann muss man aber auch richtig testen.
Irgendwie werden die reg-expr immer dann zum Problem wenn sich die produktiven Daten als umfassender herausstellen wie es die Testdaten waren.

Es ist recht schwer einen guten Regexpr zuschreiben der ALLES auf einmal macht.
Ausserdem wag ich zu behaupten das der Algorithmus im Allgemeinen und der reg-expr im Besonderen einfacher zu verstehen ist, wenn er nur auf die Untermenge der Eingabe (in diesem Fall Sätze) angewendet wird.

Das hier ist übrigens der Prototyp eines 'bösen' Regexpr.. kannst du ja mal probieren:

Code: Alles auswählen

import re
r = re.compile('(x+x+)+y')
r.search('xxxxxxxxxxxx')
r.search('xxxxxxxxxxxxxxxxxx')
r.search('xxxxxxxxxxxxxxxxxxxxxx')
r.search('xxxxxxxxxxxxxxxxxxxxxxxxx')
r.search('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
Wenn suche einen Unicodestring als text erwartet ist es die Aufgabe des Aufrufers aus seinen Daten einen zu erzeugen (der kann auch ein ' '.join ausführen
Ja da bin ich etwas übers Ziel hinaus ... dafür muß ich keine Postings von verstörten Anwender beantworten die ihre Daten via readline einlesen ;)
In regulären Ausdrücken gibt es \b für word-Boundary, das genau das macht, was Du da versuchst, kompliziert mit look-behind nachzubilden
Hab ich nicht mit '\b' zum laufen gebracht.
Dieses Bindestrichentfernen verstehe ich nicht ganz.
Ein extra Feature um ein Wort das am Zeilenende durch einen 'Silben'bindestrich getrennt wird und am Anfang der nächsten Zeile fortgesetzt wird zu finden.

Aus:
'Ich wollte. den Bus-\n
fahrer grüßen"

wird dann:
'Ich wollte. den Busfahrer grüßen"

Damit kann dann das Wort Busfahrer gefunden werden obwohl es im Eingabetext durch den Bindestrich getrennt war.
Zuletzt geändert von pyHoax am Freitag 16. Dezember 2016, 16:53, insgesamt 1-mal geändert.
BlackJack

@pyHoax: Das mit dem \b kannst Du Dir in meinem Io-Programm anschauen.

Das mit den Bindestrichen ist so eine Sache weil nicht jeder Trennstrich bei dem am Ende der Zeile umgebrochen wird, beim zusammenfügen entfernt werden darf. Und wenn man Unicode-tauglich sein will, dann gibt es da ja neben dem Trennstrich/Minus-Hybrid auch ein eigenes Trennstrich-Zeichen. Wenn wir uns schon mit Trennstrichen beschäftigen, könnte auch jemand „soft hyphens“ in den Text eingebaut haben, die man zum Suchen des Wortes heraus filtern oder im regulären Ausdruck berücksichtigen müsste. Verarbeiten von natürlicher Sprache und Grammatikregeln ist ein Fass ohne Boden. :-)
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

Ja ich hatte auch eingeräumt das es keine optimale (bzw.. nicht so gut wie ich kann) Lösung sei.

Für ein besseres Ergebnis müssten zumindest die beiden Wörter auch getrennt gefunden werden können.

In problematische Texte wie:
Im Olympiastadion treffen die Athleten ein - lauf Forest, lauf !
.. kann man den 'Einlauf' schon mal hinnehmen.
Verarbeiten von natürlicher Sprache und Grammatikregeln ist ein Fass ohne Boden
Das kann ich nur bestätigen.
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

BlackJack hat geschrieben: Das mit dem \b kannst Du Dir in meinem Io-Programm anschauen.)
Ahja : mit \\b funktioniert es, nicht aber mit \b .. hab ich da in der Dokumentation etwas verpasst ?
BlackJack

@pyHoax: Vielleicht das der Backslash in Zeichenkettenliteralen eine besondere Bedeutung hat und man '\\' schreiben muss um *sicher* einen Backslash zu bekommen weil sonst '\n' das Zeilenendezeichen, '\b' ein Backspace, usw. ist. Ich schreibe entweder immer *alle* \ doppelt oder verwende ein ”rohes” Zeichenkettenliteral wo Backslashes keine besondere Bedeutung haben.
Antworten