Funktion Zahlworte ersetzen

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
resos90
User
Beiträge: 1
Registriert: Sonntag 14. Januar 2024, 16:16

Hallo zusammen,

ich habe in der Uni eine Aufgabe bekommen, die ich aber nicht gelöst bekomme, bzw. mir sehr schwer fällt.

Die Aufgabe lautet: Erstellen Sie eine Funktion def ersetze_zahlworte(text): die alle Zahlworte (z.B. eins, zwei) aus text durch die zugehörige Ziffer ersetzt. ersetze_zahlworte("Weihnachten") liefert also "Weihn8en". Halten Sie sich bei der Programmierung der Funktionen exakt an die vorgebenen Funktionsköpfe.

Kann mir jemand von euch weiterhelfen? Ich habe gedacht, das könnten zwei Schleifen sein, wobei die äußere Schleife über den Text iteriert, die innere durch die Liste der Zahlworte. Allerdings scheitert es an der Umsetzung.

Könnte mir hier jemand helfen?

LG
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist kein sinnvolles vorgehen. Worueber soll die Schleife im Text den iterieren? Worte? Weihnachten ist dann ein wort, auf dem du dann alle moeglichen Zahlworte ersetzen sollst. Das kannst du aber auch gleich auf dem gesamten Text machen.

Stattdessen brauchst du zwei schleifen, um

- Auessere Schleife: ueber alle Ersetzungen zu laufen.
- Innere Schleife: jede Ersetzung so lange zu machen, bis keine mehr notwendig ist. Dazu gibt es verschiedene Wege, am einfachsten ist wahrscheinlich die Schleife von der Existenz des Wortes im Text abhaengig zu machen.

Wenn du Code und konkrete Fragen hast, kannst du die hier gerne posten.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

in der Aufgabe ist die Menge der Zahlen, nach denen gesucht werden soll, doch wahrscheinlich eingeschränkt, oder? Wenn ja, wie? Oder müssen wirklich alle = unendlich viele Zahlen berücksichtigt werden?

Gruß, noisefloor
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das könnte man mit einem Zahlwort-zu-Zahl Mapping lösen. Die Zahlwörter könnte man mit dem binären Or-Operator zu einem regulären Ausdruck kombinieren und dann re.sub() damit verwenden. Dort kann man ein Callable übergeben, was bei jedem Treffer ein re.Match()-Objekt erhält. Daraus kann man dann die Übersetzung in die Zahl vollziehen und das Ergebnis zurückliefern.
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@noisefloor: Wenn das der Originalaufgabentext ist, dann steht da „Ziffer“ = Einzahl, was das ganze auf 9 bis 10 Worte begrenzen würde. Sonst müsste man schauen wie die Grammatik für deutschsprachige Zahlworte aussieht und wie man die erkennen kann.

@resos90: Randfälle sind bei so etwas ja immer interessant. Wie soll beispielsweise das Ergebnis von "zweins" aussehen? "2ns", "zw1", oder "21"?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

Man könnte es auch rekursiv lösen..
In etwa so..

Code: Alles auswählen

def words_to_num(eingabe):
    newpos=-1
    test=''
    for pos, char in enumerate(eingabe):
        test=f"{test}{char}"

        if [k for k in keys if test in k]:

            if match := zahlwoerter.get(test,False):
                newpos=-1

                if pos < len(eingabe)-1:
                    return f"{match}{words_to_num(eingabe[pos+1:])}"
                return f"{match}"

            elif newpos == -1:
                newpos = pos

        elif pos < len(eingabe)-1:

            if newpos > -1:
                    return f"{test[newpos]}{words_to_num(eingabe[newpos+1:])}"
            return f"{test[pos]}{words_to_num(eingabe[pos+1:])}"

        if pos == len(eingabe)-1:
            return f"{test}"


if __name__ == '__main__':
    zahlwoerter = {
        'null': 0, 'zero': 0,
        'eins': 1, 'one': 1,
        'zwei': 2, 'two': 2,
        'drei': 3, 'three': 3,
        'vier': 4, 'four': 4,
        'fünf': 5, 'five': 5,
        'sechs': 6, 'six': 6,
        'sieben': 7, 'seven': 7,
        'acht': 8, 'eight': 8,
        'neun': 9, 'nine': 9
        }

    keys = list(zahlwoerter.keys())

    eingabe = input("Satz: ")
    ausgabe = words_to_num(eingabe)
    print(f"{eingabe} --> {ausgabe}")
Das Parsen und Ersetzen erfolgt dabei von links nach rechts..

Code: Alles auswählen

Satz: Weihnachtsmann
Weihnachtsmann --> Weihn8smann

Satz: zweins
zweins --> 2ns
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Qubit: Das funktioniert so nicht für "zero"/"null", und es ist ziemlich umständlich geschrieben.

Code: Alles auswählen

#!/usr/bin/env python3

ZAHLWOERTER = {}
for zahlwoerter in [
    "null eins zwei drei vier fünf sechs sieben acht neun",
    "zero one two three four five six seven eight nine",
]:
    ZAHLWOERTER.update(
        (zahlwort, zahl) for zahl, zahlwort in enumerate(zahlwoerter.split())
    )


def ersetze_zahlworte(eingabe):
    if eingabe == "":
        return ""

    for zahlwort, zahl in ZAHLWOERTER.items():
        if eingabe.startswith(zahlwort):
            return f"{zahl}{ersetze_zahlworte(eingabe[len(zahlwort):])}"

    return f"{eingabe[0]}{ersetze_zahlworte(eingabe[1:])}"


def main():
    eingabe = input("Satz: ")
    ausgabe = ersetze_zahlworte(eingabe)
    print(f"{eingabe} --> {ausgabe}")


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

resos90 hat geschrieben: Sonntag 14. Januar 2024, 16:21 Ich habe gedacht, das könnten zwei Schleifen sein, wobei die äußere Schleife über den Text iteriert, die innere durch die Liste der Zahlworte. Allerdings scheitert es an der Umsetzung.

Könnte mir hier jemand helfen?
Ja, das könnte man mit diesen zwei Schleifen lösen. Wie sieht denn die Umsetzung aus, und was scheitert dann genau?

Welche Funktionsköpfe sind denn vorgegeben? Gibt es weitere Einschränkungen?
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

__blackjack__ hat geschrieben: Montag 15. Januar 2024, 01:55 @Qubit: Das funktioniert so nicht für "zero"/"null", und es ist ziemlich umständlich geschrieben.
Ein Problem, in das - wer wie ich Python seltener benutzt resp. Anfänger - schnell trapsen :x
0 wird als False interpretiert.

Also besser so..

Code: Alles auswählen

    zahlwoerter = {
        'null': '0', 'zero': '0',
        'eins': '1', 'one': '1',
        'zwei': '2', 'two': '2',
        'drei': '3', 'three': '3',
        'vier': '4', 'four': '4',
        'fünf': '5', 'five': '5',
        'sechs': '6', 'six': '6',
        'sieben': '7', 'seven': '7',
        'acht': '8', 'eight': '8',
        'neun': '9', 'nine': '9'
        }
Ansonsten ist "startswith" freilich ein mächtiges Highlevel-Konstrukt. (Wie das Profile-Modul zeigt, führt das zu vielen nativen Funktionsaufrufen)
In meinem Falle - der Python primär fürs Prototyping benutzt - oder zu Lehrzwecken sollte man aber darauf lieber verzichten.
Es kommt jetzt darauf an, welche Anforderungen eigentlich an die Funktionen gestellt wurden. Diese Auskunft ist uns der Threadersteller noch schuldig..
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Qubit: Was ist denn das für eine verquere Logik? Weil es lesbaren kurzen Code ermöglicht, sollte man es für's Prototyping und Lehrzwecke meiden? Und was heisst „mächtiges Highlevel-Konstrukt“? Das ist eine simple Methode ob eine Zeichenkette eine andere Zeichenkette als Präfix hat. Wenn es die Methode nicht gäbe, wäre es trivial diesen Test selbst zu schreiben, nur wäre der in Python geschrieben dann langsamer als die in C geschriebene Methode.

Wenn man das low-level lösen möchte, ohne zusätzliche Importe, könnte man sich einen kleinen Zustandsautomaten basteln mit dem man Zahlworte erkennen kann.

Code: Alles auswählen

#!/usr/bin/env python3

ZAHLWORT_ZU_ZIFFER = {
    zahlwort: str(wert)
    for wert, zahlwort in enumerate(
        "null eins zwei drei vier fünf sechs sieben acht neun".split()
    )
}
ZUSTANDSUEBERGAENGE = {
    (0, "a"): (1, False),
    (1, "c"): (2, False),
    (2, "h"): (3, False),
    (3, "t"): (4, True),
    (0, "d"): (5, False),
    (5, "r"): (6, False),
    (6, "e"): (7, False),
    (7, "i"): (8, True),
    (0, "e"): (9, False),
    (9, "i"): (10, False),
    (10, "n"): (11, False),
    (11, "s"): (12, True),
    (0, "f"): (13, False),
    (13, "ü"): (14, False),
    (14, "n"): (15, False),
    (15, "f"): (16, True),
    (0, "n"): (17, False),
    (17, "e"): (18, False),
    (18, "u"): (19, False),
    (19, "n"): (20, True),
    (17, "u"): (21, False),
    (21, "l"): (22, False),
    (22, "l"): (23, True),
    (0, "s"): (24, False),
    (24, "e"): (25, False),
    (25, "c"): (26, False),
    (26, "h"): (27, False),
    (27, "s"): (12, True),
    (24, "i"): (29, False),
    (29, "e"): (30, False),
    (30, "b"): (31, False),
    (31, "e"): (32, False),
    (32, "n"): (20, True),
    (0, "v"): (33, False),
    (33, "i"): (34, False),
    (34, "e"): (35, False),
    (35, "r"): (36, True),
    (0, "z"): (37, False),
    (37, "w"): (38, False),
    (38, "e"): (7, False),
}


def ersetze_zahlworte(text):
    ergebnis = []
    zustand = i = j = 0
    while j < len(text):
        zustand, ist_zahlwort = ZUSTANDSUEBERGAENGE.get(
            (zustand, text[j]), (0, False)
        )
        j += 1
        if zustand == 0:
            if j - i > 1:
                j -= 1
            ergebnis.append(text[i:j])
            i = j
        elif ist_zahlwort:
            ergebnis.append(ZAHLWORT_ZU_ZIFFER[text[i:j]])
            i = j
            zustand = 0

    return "".join(ergebnis)


def main():
    eingabe = input("Satz: ")
    ausgabe = ersetze_zahlworte(eingabe)
    print(f"{eingabe} --> {ausgabe}")


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

__blackjack__ hat geschrieben: Donnerstag 18. Januar 2024, 10:33 Wenn man das low-level lösen möchte, ohne zusätzliche Importe, könnte man sich einen kleinen Zustandsautomaten basteln mit dem man Zahlworte erkennen kann.
[..]
Verrückt, was du immer so aus dem Hut zauberst.. :D
Benutzeravatar
DeaD_EyE
User
Beiträge: 1021
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Qubit hat geschrieben: Dienstag 16. Januar 2024, 18:59 Ein Problem, in das - wer wie ich Python seltener benutzt resp. Anfänger - schnell trapsen :x
0 wird als False interpretiert.
Das gehört zu den Basics und wenn man etwas länger Python einsetzt, dann nutzt man das auch aktiv.
Leere listen und leere Tuple liefert auch ein False zurück, wenn die Funktion bool angewandt wird usw...
Qubit hat geschrieben: Dienstag 16. Januar 2024, 18:59 Ansonsten ist "startswith" freilich ein mächtiges Highlevel-Konstrukt. (Wie das Profile-Modul zeigt, führt das zu vielen nativen Funktionsaufrufen)
In meinem Falle - der Python primär fürs Prototyping benutzt - oder zu Lehrzwecken sollte man aber darauf lieber verzichten.
Es kommt jetzt darauf an, welche Anforderungen eigentlich an die Funktionen gestellt wurden. Diese Auskunft ist uns der Threadersteller noch schuldig..
Das mag sinnvoll sein, wenn man startswith verstehen will. Wenn an aber eine Idee in Code ausdrücken will, ist weniger Code und gut lesbarer Code wichtiger.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Zustandsautomaten zur Worterkennung gibt's ja netterweise schon fertig:

Code: Alles auswählen

#!/usr/bin/env python3
import re

ZAHLWORT_RE = re.compile(
    "|".join(
        map(
            "({})".format,
            "null eins zwei drei vier fünf sechs sieben acht neun".split(),
        )
    )
)


def ersetze_zahlworte(text):
    return ZAHLWORT_RE.sub(lambda match: str(match.lastindex - 1), text)


def main():
    eingabe = input("Satz: ")
    ausgabe = ersetze_zahlworte(eingabe)
    print(f"{eingabe} --> {ausgabe}")


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

das ist für mich wieder so ein faszinierendes Beispiel. Auf ein mal ist die Lösung so einfach, kurz und sauber. 👍🏼

Wobei sie für mich bei genauerem hinsehen, dann noch nicht soooo klar ist.
Ich verstehe den `lambda`-Ausdruck nicht ganz. Es ist mir klar, das `match` ein `re`-Objekt ist und ich lese den Ausdruck so:
Eine Funktion bekommt das Argument `match` und gibt `str(match.lastindex -1)` zurück.(?) Was mir schleierhaft ist, wo entsteht `match`? Habe ich Tomaten auf den Augen?

Wenn ich zum Beispiel sehen will was `match.lastindex - 1` zurückgibt, dann muss ich zum Beispiel mal `search` aufrufen, damit ich ein `match`-Objekt erhalten:

Code: Alles auswählen

def ersetze_zahlworte(text):
    match = ZAHLWORT_RE.search(text)
    ic(match.lastindex - 1)
    return ZAHLWORT_RE.sub(lambda match: str(match.lastindex - 1), text)
Jetzt brauche ich `lambda` natürlich nicht mehr, deswegen meine Frage, wie wird das `match`-Objekt her gezaubert?


Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Dennis89: `match` ist ein `re.Match`-Objekt, Doku hier. Und wo das herkommt, ist in der Doku für `re.sub` beschrieben.
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

__blackjack__ hat geschrieben: Montag 29. Januar 2024, 16:10 Zustandsautomaten zur Worterkennung gibt's ja netterweise schon fertig:
Sehr schöne Lösung :D

Könnte man jetzt auch mit einem Dictionary verwursten..

Code: Alles auswählen

import re

ZAHLWOERTER = {
    'null': '0', 'zero': '0',
    'eins': '1', 'one': '1',
    'zwei': '2', 'two': '2',
    'drei': '3', 'three': '3',
    'vier': '4', 'four': '4',
    'fünf': '5', 'five': '5',
    'sechs': '6', 'six': '6',
    'sieben': '7', 'seven': '7',
    'acht': '8', 'eight': '8',
    'neun': '9', 'nine': '9',
    'Ruby': 'Python'
    }

ZAHLWORT_RE = re.compile(
    "|".join(ZAHLWOERTER.keys())
)


def ersetze_zahlworte(text):
    return ZAHLWORT_RE.sub(lambda match: ZAHLWOERTER[match.group()], text)


def main():
    eingabe = input("Satz: ")
    ausgabe = ersetze_zahlworte(eingabe)
    print(f"{eingabe} --> {ausgabe}")


if __name__ == "__main__":
    main()
Satz: Der Weihnachtsmann kennt das ein mal eins und nicht zweins von Ruby
Der Weihnachtsmann kennt das ein mal eins und nicht zweins von Ruby --> Der Weihn8smann kennt das ein mal 1 und nicht 2ns von Python
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

@narpfel
If repl is a function, it is called for every non-overlapping occurrence of pattern. The function takes a single Match argument, and returns the replacement string. [/qoute]
Oh das habe ich vorhin übersehen. Vielen Dank 😊


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dennis89 hat geschrieben: Montag 29. Januar 2024, 21:53 @narpfel
If repl is a function, it is called for every non-overlapping occurrence of pattern. The function takes a single Match argument, and returns the replacement string.
Oh das habe ich vorhin übersehen. Vielen Dank 😊


Grüße
Dennis
Wenn das deine favorisierte Lösung ist, dann hast du die vor 2 Wochen schon als eine der ersten Antworten übersehen (viewtopic.php?p=426268#p426268). ;)

Das Potenzial von ``re`` lässt sich noch weiter ausnutzen, wenn man beim Kompilieren des Ausdrucks re.IGNORECASE mitgibt. Damit muss für die Ersetzung nicht mehr auf Groß- und Kleinschreibung geachtet werden.

Übrigens, da noch niemand replace() erwähnt hat:

Code: Alles auswählen

for i, wort in enumerate(zahlworte):
    text = text.replace(wort, str(i))
Ein Nachteil daran, der hier aber kein spürbares Performance-Problem darstellt: replace() gibt halt nach jeder Ersetzung aller Vorkommen des jeweiligen Zahlworts den ersetzten Text zurück. Mit re.sub() hat man diesen Zwischenschritt nicht. Weiterhin dürfte sich bei (konstruierten) Randfällen das Ersetzungsverhalten zwischen den beiden Ansätzen unterscheiden. Und replace() kennt auch kein "ignorecase".
Antworten