Silbentrennungsprogramm

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
HeroLinkGHG
User
Beiträge: 1
Registriert: Mittwoch 12. November 2025, 21:54

Hallo,

ich bin neu und habe es mir zur Aufgabe gemacht, ein Programm zu schreiben, dass eine .txt einliest, die Zeilen und Wörter trennt und mir dann nach einigen von mir festgelegten Regeln "-" einfügt. Die Regeln sind: Der erste und letzte Buchstabe dürfen nicht abgetrennt werden, ein wort kann zwischen zwei Konsonanten getrennt werden, bei drei aufeinanderfolgenden Konsonanten kann nur nach dem ersten Konsonanten getrennt werden und ein Wort kann nach einem Vokal getrennt werden, wenn nicht zwei Konsonanten folgen.
Bisher habe ich diesen Code:

Code: Alles auswählen

import os
Vokale = ["a", "e", "i", "o", "u","A", "E", "I", "O", "U", "ä", "ö", "ü", "Ä", "Ö", "Ü"]
Konsonanten = ["b", "B", "c", "C", "d", "D", "f", "F", "g", "G", "h", "H", "j", "J", "k", "K", "l", "L", "m", "M", "n", "N", "p", "P", "q", "Q", "r", "R", "s", "S", "t", "T", "v", "V", "w", "W", "x", "X", "y", "Y", "z", "Z", "ß"]
while True:
    getrennterText =[]
    print("Bitte geben Sie den Dateipfad der zu öfnnenden Datei an.")
    Dateipfad = input()
    with open (Dateipfad, mode="r") as D:
        Dateiinhalt = D.read().splitlines()
        Dateilänge = len(Dateiinhalt)
        aZeile = 0
    for i in range(0, Dateilänge):
        aWort = 0
        Zeileninhalt = Dateiinhalt[aZeile].split(" ")
        Name_Zeileninhalt = f"Z{aZeile + 1}"
        if Name_Zeileninhalt not in globals():
            globals()[Name_Zeileninhalt] = []
        globals()[Name_Zeileninhalt].append(Zeileninhalt)
        Zeilenlänge = len(Zeileninhalt)
        for j in range(0, Zeilenlänge):
            getrennteZeile = []
            Trennung = []
            Wortinhalt = Zeileninhalt[aWort].split()
            Name_Wortinhalt = f"{Name_Zeileninhalt}W{aWort + 1}"
            if Name_Wortinhalt not in globals():
                globals()[Name_Wortinhalt] = []
            globals()[Name_Wortinhalt].append(Wortinhalt)
            Wortlänge = len(Wortinhalt)
            for k in range(1, Wortlänge - 1):
                aBuchstabe = Wortinhalt[k].lower()
                aBuchstabe_plus_eins = Wortinhalt[k + 1].lower()
                aBuchstabe_plus_zwei = Wortinhalt[k + 2].lower()
                aBuchstabe_minus_eins = Wortinhalt[k - 1].lower()
                aBuchstabe_minus_zwei = Wortinhalt[k - 2].lower()
                if aBuchstabe_minus_eins in Vokale:
                    if aBuchstabe in Konsonanten and aBuchstabe_plus_eins in Konsonanten and aBuchstabe_minus_eins in Konsonanten:
                        pass
                    else:
                        Trennung.append(k)
                    if aBuchstabe in Vokale and aBuchstabe_plus_eins in Vokale:
                        Trennung.append(k)
            getrenntesWort = ""
            for l in range(Wortlänge):
                if l in Trennung:
                    getrenntesWort += "-"
                getrenntesWort += Wortinhalt[l]
            getrennteZeile.append(getrenntesWort)
            getrennterText.append(getrennteZeile)
            aWort = aWort + 1
        aZeile = aZeile + 1
        print(getrennterText)
Ich bekomme aber nicht die Antwort, die ich erwarte. Kann mir jemand erkären, woran das liegt und wie ich die von mir genannten Regeln hinzufügen kann, damit der Code noch funktioniert?

Vielen Dannk im Vorraus
HeroLinkGHG
PS: Ich weiß, dass die Verwendung von globals() und vielen anderen Sachen nicht schön ist, aber diese würde ich gerne behalten, damit ich das an meinem Code besser nachvollziehen kann.
Benutzeravatar
__blackjack__
User
Beiträge: 14226
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@HeroLinkGHG: `globals()` macht das kein Stück besser nachvollziehbar als wenn man ein eigenes Wörterbuch dafür verwenden würde und das wäre auch sicherer. Zumal ich auf eine Erklärung gespannt bin was das überhaupt bewirken soll, denn wenn ich das richtig sehe kann man den ganzen Unsinn mit `globals()` da einfach raus löschen ohne das sich etwas am Programmablauf ändert‽

Es wäre auch sinnvoll wenn Du beschreiben würdest wo genau das Problem liegt. Also was konkret fütterst Du in das Programm hinein, was hast Du erwartet was raus kommt, und warum, und wie weicht das von dem erwarteten ab?

`os` wird importiert, aber nirgends verwendet.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Einbuchstabige Namen sind selten gute Namen. Das geht für Laufvariablen oder welche die als Index verwendet werden für `i`, `j`, und `k` oder für `x`, `y`, `z` für Koordinaten, weil das jeder aus der Mathematik kennt, aber ein Dateiobjekt sollte nicht einfach nur `d` heissen sondern `datei`.

Man verwendet keine kryptischen Prä- oder Suffixe. Es ist zum Beispiel nicht klar, warum an einige Namen wie `a_wort` oder `a_zeile` dieses `a_` vorangestellt wurde. Namen sollen dem Leser Informationen vermitteln, nicht zusätzliche Fragen aufwerfen.

Statt die Buchstaben selbst manuell alle hin zu schreiben, könnte man auf `string.ascii_letters` aufbauen. Statt Listen wären Mengen hier sinnvoller und effizienter. Wesentlich weniger Gefahr sich zu vertippen wenn man möglichst viel davon berechnet:

Code: Alles auswählen

from string import ascii_letters

_VOKALE = "aeiouäöü"
VOKALE = set(_VOKALE + _VOKALE.upper())
del _VOKALE
KONSONANTEN = set(ascii_letters + "ß") - VOKALE
Beim öffnen von Dateien sollte man explizit die Kodierung angeben. Sonst ”rät” Python und das kann auch falsch sein.

`i` und `j` werden nicht verwendet, stattdessen gibt es `a_zeile` und `a_wort` die extra initialisiert und hochgezählt werden, und dadurch immer den gleichen Wert wie `a_zeile` und `a_wort` haben. Letztlich sind aber `i`, `j`, `a_wort`, und `a_zeile` komplett überflüssig, denn man kann in Python direkt über die Elemente von Sequenztypen iterieren, ohne den Umweg über einen Laufindex. Was den Code einfacher und damit auch einfacher verständlich macht.

`getrennte_zeile` wird recht weit vor der Stelle definiert bevor es dann letztlich benutzt wird. Wenn man das erst kurt vorher definiert, fällt einem direkt ein Problem damit auf: da wird grundsätzlich eine Liste erstellt, an die genau ein einziges Element angehängt wird. Das sieht nicht besonders sinnvoll aus.

`buchstabe_plus_zwei` und `buchstabe_minus_zwei` werden definiert, aber nirgends verwendet. Letzteres ist auch problematisch, denn das enthält einmal etwas das so sicher nicht gewollt ist. Probier das mal für den ersten Schleifendurchlauf aus.

Die `lower()`-Aufrufe sind unnötig, da `KONSONANTEN` und `VOKALE` auch Grossbuchstaben enthalten.

Die Trennindizes würde man eher in einem `set()` sammeln. Dann hätte man auch nicht das Problem, dass ein Index mehr als einmal vorkommen kann, denn in dem gezeigten Code könnte das passieren.

Nur ein ``pass`` in einem ``if``- oder ``else``-Zweig macht keinen Sinn. Bei ``else`` würde man einfach den Zweig weglassen, bei ``if`` in einem ``if``/``else`` die Bedingung negieren und den ``else``-Zweig in den ``if``-Zweig verschieben.

Dann haben die beiden ``if``\s den gleichen Inhalt, man kann die also einfach per ``or`` zusammenfassen.

`l` wird benötigt, aber man würde das nicht mit `range()` generieren. Wenn man *zusätzlich* zu den einzelnen Elementen einer Sequenz eine laufende Zahl benötigt, verwendet man `enumerate()`.

Wiederholtes erweitern eine Zeichenkette mit ``+``/``+=`` in einer Schleife ist potentiell sehr ineffizient, weil Zeichenketten unveränderlich sind, und da immer neue, immer länger werdende Zeichenketten erzeugt werden können. Idiomatisches Python sammelt die Teilzeichenketten in einer Liste oder verwendet einen Generatorausdruck und die `join()`-Methode auf Zeichenketten.

Grundsätzlich ist das zu viel Code in einem ”Klumpen” der schlecht testbar ist. Das Problem sollte auf kleinere Teilprobleme aufgeteilt werden, die durch einzelne Funktionen gelöst werden. Dann kann man das viel leichter Testen und auch den Code vereinfachen. Zum Beispiel eine Funktion die genau ein Wort bekommt und das trennt.
“Ich bin für die Todesstrafe. Wer schreckliche Dinge getan hat, muss eine angemessene Strafe bekommen. So lernt er seine Lektion für das nächste Mal.” — Britney Spears, Interview in der französischen Zeitung Libération, 2. April 2002
Sirius3
User
Beiträge: 18328
Registriert: Sonntag 21. Oktober 2012, 17:20

Was für eine Antwort bekommst Du, und wie weicht diese von der Erwartung ab?

__blackjack__ hat jetzt ja schon einiges geschrieben, aber vielleicht wiederholt sich ja nicht alles:

Programme sollten in Funktionen strukturiert sein, damit man einzelne Teile (z.B. das Auftrennen EINES Wortes) separat testen kann.
Dazu gibt es einige Konventionen, die man von Anfang an einhalten sollte, damit das Lesen des Codes einfacher ist und auch andere schneller den Code verstehen, vor allem, damit sich irgendwer hier im Forum damit beschäftigen möchte:
Variablen werden generell komplett klein geschrieben. Konstanten dagegen KOMPLETT_GROSS, damit man gleich sieht, was konstant ist und was nicht.
Präfixe wie `a` tragen nichts zum Verständnis bei. In wie weit unterscheidet sich eine Variable `buchstabe` von `aBuchstabe`?
Über einen Index iteriert man nicht, weil man direkt über die Element einer Liste iterieren kann.
Auf `globals()` sollte man gar nicht zugreifen. Das, was Du da machst, kann man durch eine einfache Liste ersetzen.
`j` wird nie benutzt, dafür wird aber händisch parallel dazu `aWort` hochgezählt.
Wenn man an Leerraum aufteilen möchte ruft man am besten `spilt` ohne Argument auf, denn dann wird jeder Leerraum (auch mehrere Leerzeichen hinereinander, Tabs, etc.) als eins gezählt.
Dann kommt ein möglicher Fehler, weil Du auf ein Wort nochmals `split` aufrufst.
Was Du ganz innen machst, ist dann falsch, weil Du über einen Index auf einzelne Buchstaben zugreifst, der Index muß aber gar nicht existieren, was dann zu einem IndexError führt.
`l` ist als Variablenname gänzlich ungeeignet, weil er viel zu leicht als 1 zu lesen ist.
Bei getrennteZeile und getrennterText geht einiges durcheinander, ich vermute, da stimmt die Einrückung nicht.

Nach einem ersten Überarbeitungsschritt könnte das Programm so aussehen, wobei die Funktion trenne_wort noch falsch ist, da mußt Du Dir einen besseren Algorithmus überlegen.

Code: Alles auswählen

VOKALE = ["a", "e", "i", "o", "u", "ä", "ö", "ü"]
KONSONANTEN = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z", "ß"]

def trenne_wort(wort):
    trennung = []
    # ab hier kann der Algorithmus nicht mehr funktionieren
    for k in range(1, len(wort) - 1):
        buchstabe = wort[k].lower()
        buchstabe_plus_eins = wort[k + 1].lower()
        buchstabe_plus_zwei = wort[k + 2].lower()
        buchstabe_minus_eins = wort[k - 1].lower()
        buchstabe_minus_zwei = wort[k - 2].lower()
        if buchstabe_minus_eins in VOKALE:
            if not (buchstabe in KONSONANTEN and buchstabe_plus_eins in KONSONANTEN and buchstabe_minus_eins in KONSONANTEN):
                trennung.append(k)
            if buchstabe in VOKALE and buchstabe_plus_eins in VOKALE:
                trennung.append(k)
    getrenntes_wort = ""
    for k, buchstabe in enumerate(wort):
        if k in trennung:
            getrenntes_wort += "-"
        getrenntes_wort += buchstabe
    return getrenntes_wort


def main():
    while True:
        print("Bitte geben Sie den Dateipfad der zu öfnnenden Datei an.")
        dateipfad = input()
        with open(dateipfad, mode="r") as zeilen:
            datei_inhalt = list(zeilen)

        getrennter_text =[]
        zeilen_inhalte = []
        for zeile in datei_inhalt:
            zeilen_inhalt = zeile.split()
            zeilen_inhalte.append(zeilen_inhalt)
            getrennte_zeile = []
            for wort in zeilen_inhalt:
                getrenntes_wort = trenne_wort(wort)
                getrennte_zeile.append(getrenntes_wort)
            getrennter_text.append(getrennte_zeile)
        print(getrennter_text)

if __name__ == "__main__":
    main()
Benutzeravatar
noisefloor
User
Beiträge: 4244
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

also Programmierübung ist das ja ganz nett - aber wenn da am Ende was sinnvolles, also im Sinne von einer validen Liste mit getrennten Wörter rauskommen soll, sind die Regeln in Teilen falsch.
bei drei aufeinanderfolgenden Konsonanten kann nur nach dem ersten Konsonanten getrennt werden
Das ist falsch. "Sauerstofftank" wird "Sau-er-stoff-tank" getrennt, also bei fft nach dem 2. Konsonanten.
und ein Wort kann nach einem Vokal getrennt werden, wenn nicht zwei Konsonanten folgen.
Das ist falsch. Z.B. können "Laub", "Sieg" und "Baum" gar nicht getrennt werden - die o.g. Regel würde aber zutreffen.

Ggf. macht es auch Sinn, erstmal auf die Wortlänge zu prüfen bzw., ob das Wort überhaupt trennbar ist Wenn die < 4 ist, kann sowieso nicht getrennt werden. Wenn die Wortlänge 4 ist, kann nicht getrennt, wenn Buchstabe 2 und 3 au oder eu oder ie oder ei sind, usw.

Wie gesagt, als Programm interessant, aber das Regelwerk ist ziemlich komplex, wenn man 100% korrekt trennen möchte.

Gruß, noisefloor
nezzcarth
User
Beiträge: 1785
Registriert: Samstag 16. April 2011, 12:47

Ich möchte das, was noisefloor schreibt noch Mal unterstreichen: Das ist ein komplexes Thema. Die Entwicklung des Algorithmus' in TeX, der die Silbentrennung durchführt, war damals Gegenstand einer Doktorarbeit (https://tug.org/docs/liang/).

Wenn es also um die ernsthafte Anwendbarkeit geht, müsste man noch Mal ganz anders in das Thema einsteigen. Wenn es nur um eine einfache Programmierübung handelt, ist das aber vmtl. übertrieben.
Antworten