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.
BlackJack

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.

Komplett ohne reguläre Ausdrücke könnte man es in C# (weil das in anderen Sprachen ja geht, nur in Python nicht) so machen:
[codebox=csharp file=Unbenannt.cs]using System;
using System.Collections.Generic;
using System.IO;

class ForumMain {

private static string EndOfSentence = ".";

private static IEnumerable<String> ParagraphToSentences(string paragraph)
{
foreach (string sentence in paragraph.Split(EndOfSentence.ToCharArray())) {
string trimmedSentence = sentence.Trim();
if (trimmedSentence.Length != 0) {
yield return trimmedSentence + EndOfSentence;
}
}
}

private static IEnumerable<String> ParagraphsToSentences(IEnumerable<String> paragraphs)
{
foreach (string paragraph in paragraphs) {
foreach (string sentence in ParagraphToSentences(paragraph)) {
yield return sentence;
}
}
}

private static bool IsWordBoundary(char c)
{
return Char.IsWhiteSpace(c) || !Char.IsLetter(c);
}

private static bool ContainsWord(string sentence, string word)
{
int startIndex = sentence.IndexOf(word);
if (startIndex == -1) {
return false;
}
int endIndex = startIndex + word.Length;
return (startIndex == 0
|| IsWordBoundary(sentence[startIndex - 1]))
&& (endIndex == sentence.Length - 1
|| IsWordBoundary(sentence[endIndex]));
}

private static IEnumerable<String> SearchForWord(IEnumerable<String> sentences, string word)
{
foreach (string sentence in sentences) {
if (ContainsWord(sentence, word)) {
yield return sentence;
}
}
}

public static void Main(string[] args) {
if (args.Length < 2) {
Console.WriteLine("usage: search.exe <filename> <word>");
} else {
string filename = args[0];
string word = args[1];
IEnumerable<String> paragraphs = File.ReadAllLines(filename);
IEnumerable<String> sentences = ParagraphsToSentences(paragraphs);
IEnumerable<String> filteredSentences = SearchForWord(sentences, word);
foreach (string sentence in filteredSentences) {
Console.WriteLine(sentence);
}
}
}
}[/code]
Man sieht schön wie man so ein Problem auf kleinere Teilprobleme herunter bricht, die man dann mit wenigen Zeilen Code Lösen kann. Testlauf:
[codebox=text file=Unbenannt.txt]./forum.exe tale.txt Luzifer
Dort trifft er auf seinen Boss Luzifer, den Richter.
Pearly ersucht Luzifer um die Genehmigung, ausnahmsweise Zugang zu einer Adresse außerhalb der Stadt zu bekommen: zu Beverlys Haus am See; der hohe Richter Luzifer lehnt Pearlys Antrag ab.
Pearly hat mit Luzifer einen Deal ausgehandelt: Wenn er bereit sei, sich sterblich zu machen, dürfe er Peter auch außerhalb der Stadt verfolgen.[/code]
*Mit* regulären Ausdrücken würde ich die Grundstruktur beibehalten: Aufteilen in Sätze und Suchen nach Wort getrennt implementieren. Das sollte sowohl einfach, als auch lesbar sein/bleiben und man könnte relativ einfach mehr als den Punkt als Satzende verwenden. Der Beispieltext hat allerdings nur Punkte als Satzenden.
harvey186
User
Beiträge: 20
Registriert: Mittwoch 14. Dezember 2016, 08:20

@BlackJack
Danke für die Mühe.
Aber ich glaube ich komme hier nicht weiter, denn eure Erklärungen verstehe ich genauso wenig wie Python selbst. Ich muss wohl noch ein paar Wochen Frust investieren, bis ich weiß, wie ich den Text Satzweise trennen und wegschreiben kann. Wie gesagt, Absatzweise gehts ja, aber nicht Satzweise :(
BlackJack

@harvey186: Das hat nicht wirklich etwas mit Python speziell zu tun, denn das Vorgehen ist in so ziemlich allen gängigen Programmiersprachen gleich. Wie man an dem C#-Programm weiter oben sehen kann. Wie gesagt: Das ganze in kleinere Teilprobleme aufteilen. Du musst nicht lösen wie man den Text Satzweise wegschreiben kann, sondern wie man den Text in Absätze aufteilt (1 Funktion), einen Absatz in Sätze aufteilt (1 Funktion), Absätze in Sätze umwandelt (1 Funktion die trivial ist wenn man das Teilproblem *einen* Absatz in Sätze aufteilen gelöst hat), Sätze wegschreiben (1 Funktion, die auch wieder trivial ist, weil man die Sätze da ja schon hat). Das zusammen ergibt dann „Absätze einlesen und Sätze wegschreiben.“

Wobei das mit dem Sätze wegschreiben vom Ursprungsproblem abweicht, das war ja noch ein Wort in Satz suchen. Was auch wieder eine einzelne Funktion ist, denn die Zerlegung der Sätze hat man davor ja schon mittels der anderen Funktionen gelöst. Und auch diese Funktion stützt sich gegebenenfalls auf eine oder mehrere andere Funktionen ab, die Teilprobleme davon lösen.

Als allererstes musst Du den Umgang mit den Kontrollstrukturen (Schleifen, bedingte Ausführung, …), Grunddatentypen (Zahlen, Zeichenketten, Listen, …), und Funktionen (also wie man dies selbst schreibt) lernen. Das ist in Python so, wäre aber auch in jeder anderen Programmiersprache so. Wenn man die grundlegenden Werkzeuge und Spracheigenschaften nicht kennt und beherrscht, kann man sie halt auch nicht anwenden um Probleme zu lösen. Dazu sollte man eigentlich nicht „ein paar Wochen“ brauchen. Ein paar Tage sollten da reichen. Und ob man das mit Frust macht, bleibt jedem selbst überlassen, aber es ist sicher nicht die beste, produktivste Stimmung um etwas zu lernen.
harvey186
User
Beiträge: 20
Registriert: Mittwoch 14. Dezember 2016, 08:20

Mit rpg würde ich normalerweise sowas schreiben wie

Zeile lesen
wenn Zeile ungleich BLANKS dann
Ausgabe
bzw. in Datei schreiben.
in Python würde ich das so machen wollen, aber es geht halt nicht

Code: Alles auswählen

f = open("tale.txt", "r")
fobj_out = open("tale1.txt","w")
line = f.readline()
for line in f:
    if line != " ":
        ln =
        print(line, end="")
        fobj_out.write(line)
Zuletzt geändert von Anonymous am Donnerstag 15. Dezember 2016, 13:51, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@harvey186: Python ist halt nicht RPG, so wie so ziemlich jede andere Programmiersprache nicht RPG ist. Zumal ich jetzt nicht sehe wie das Programm welches Du da in Pseudocode für RPG skizziert hast Dein Problem aus dem Ausgangsbeitrag löst. Das scheint mir nur nicht-leere Zeilen auszugeben beziehungsweise zu kopieren.

Das macht Dein Python-Programm ja auch fast, wenn man drei kleine Änderungen vornimmt. 1. Nicht vor der Schleife die erste Zeile der Datei lesen und dann nichts damit tun (es sei denn das war Absicht das die erste Zeile der Eingabedatei ignoriert wird). 2. Den Syntaxfehler mit der Zuweisung an `ln` beseitigen wo auf der rechten Seite nichts steht. 3. Der Test den Du machst ist ob `line` ungleich der Zeichenkette mit einem Leerzeichen ist. Das ist nicht das was Du willst. Da gibt es zwei Möglichkeiten das zu lösen wenn man sich mal die Methoden anschaut die auf Zeichenketten in Python definiert sind. Bei einer der Lösungen muss man noch wissen wie Python den Wahrheitsgehalt eines Wertes bei Bedingungen bestimmt.

Das sind alles Grundlagen. Die muss man lernen wenn man in Python programmieren möchte. Wie bei jeder anderen Programmiersprache auch. Das müsste ich bei RPG auch wenn ich damit diese Aufgabe lösen wollte. Da kann ich mich auch nicht hinstellen und ein bisschen im Netz suchen und ein paar Sachen ausprobieren und dann sagen, och in Python geht das aber, RPG frustriert mich. Da muss ich ja wochenlang frustriert lernen bevor ich das lösen kann.
harvey186
User
Beiträge: 20
Registriert: Mittwoch 14. Dezember 2016, 08:20

Ich habe damit nur das angedeutet, was hier immer wider gesagt wird: Kleine Schritte.
Die Leerzeile weg nehmen wäre Schritt eins gewesen, aber schon daran scheitere ich.

Gibt es eigentlich irgendwo eine Befehlsübersicht,
wo ich mir die Befehle anschauen kann und welche Parameter sie brauchen ?
also z.B. Datei ganz einlesen --- read() -- Parameter blah blah
Zeilenweise lesen readline () -- Parameter blah blah bla
Datei öffnen --- open () --Parameter blah, blah
u.s.w.
hab ledier nichts per Google gefunden
BlackJack

@harvey186: Das steht alles in der Python-Dokumentation. Die gibt's da wo Python her kommt: python.org

Wobei ich es jetzt nicht Befehlsübersicht nennen würde, denn Befehle sind was anderes und davon gibt's in Python nicht wirklich viele. Aber auch die sind natürlich in der Dokumentation zu finden.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Was wird eigentlich aus Sätzen, die Punkte enthalten, welche kein Satzende kennzeichnen? Beispiele:
Er ließ sich lieber von Dr. Schmidt untersuchen.
Wir haben viel Obst, z.B. Äpfel, Bananen und Birnen.
Das 3. Buch Mose.
Ich denke, für diese Fälle muss man einfach hinnehmen, dass eine rein syntaktische Analyse hier an ihre Grenzen stößt. Man sieht aber, wie komplex das Ganze werden kann, wenn man alle Feinheiten der Sprache beachten möchte.
BlackJack

@snafu: Schwierig. Insbesondere weil bei einigen Abkürzungen der Punkt gleichzeitig auch das Ende des Satzes sein kann. Wie bei ”usw.” wenn es am Satzende steht. Und auch tatsächliche Satzende-Punkte können problematisch sein wenn sie in zitiertem, gesprochenen Wort stehen. Um es mit Homer S.' oder (S'.?) Worten zu sagen: „Doh.“ Da ist ein Punkt für das Satzende *nach dem* noch ein Anführungszeichen kommt, welches nicht zum nächsten Satz gehört. Und ein Fragezeichen im Satz das nicht den Satz beendet. Und was ist mit meinem Lieblingssatzzeichen‽ ← Ja genau das da. :-) Oder die Spanier stellen Satzzeichen ja auch manchmal voran und/oder auf den Kopf (¿Que?). Die […] Auslassungpunkte können auch mitten drin oder am Ende stehen… Über Programmiersprachen darf man auch nicht schreiben. Python geht ja noch, aber Negation (!) oder der Ternärer Operator ((cond) ? iftrue : iffalse;) in Sprachen die an C-Syntax angelehnt sind, bereiten Probleme. Natürliche Sprachen sind lustig. :-D
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier mal ein erster naiver Ansatz, um Sätze zu trennen:

Code: Alles auswählen

import re

def main():
    text = 'Der Hase liegt im Gras. Das Kind schaut ihm dabei zu. Aber plötzlich passiert es: Der Hase hoppelt davon!'
    sentences = re.split(r"(?<=[.!?]) ", text)
    print('\n'.join(sentences))

if __name__ == '__main__':
    main()
Das finde ich noch halbwegs lesbar trotz regulären Ausdrücken: Trenne an Leerzeichen, wenn diesen ein Punkt, Ausrufezeichen oder Fragezeichen vorangestellt ist. Wenn man noch wörtliche Rede reinbringen will, dann wird es schon komplizierter. Aber auf dieser Basis müsste man nur noch das Ergebnis durchgehen und schauen, in welchen Sätzen das Suchwort enthalten ist. Was natürlich noch nicht bedacht wurde, sind mehrzeilige Texte, wo ein Satz natürlich auch einen Zeilentrenner enthalten kann.
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: 6740
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: 6740
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: 6740
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: 6740
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: 6740
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: 6740
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.
Antworten