Zahl und Text trennen

Django, Flask, Bottle, WSGI, CGI…
Antworten
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Bei den Aufgaben zu Umfang und Fläche von Figuren, möchte ich sicherstellen, dass die Schülerinnen und Schüler auch die richtige Einheit verwenden. Zur Auswertung müsste ich Zahl und Text trennen, das habe ich noch nicht wirklich hinbekommen. Wenn ich sicherstellen könnte, dass ein Leerzeichen dazwischen wäre, die Eingabe immer eine Ganzzahl wäre oder der erste Buchstabe immer gleich, würde ich das ja vielleicht noch hinbekommen. Es könnten aber so unterschiedliche Eingaben wie "24mm²" oder "2,4cm" sein. Mihilfe des Forums hier ist es mir schon gelungen den Zahlenwert auszulesen.

Code: Alles auswählen

zahl_eingabe = [float(s) for s in re.findall(r'-?\d+\.?\d*', eingabe)]
und:

Code: Alles auswählen

            zahl_loe = [float(s) for s in re.findall(r'-?\d+\.?\d*', loe)]
            zahl_eingabe = [float(s) for s in re.findall(r'-?\d+\.?\d*', eingabe)]
            try:
                if zahl_eingabe[0] == zahl_loe[0]:
                    return 0.5, "Die Zahl stimmt, die Einheit aber nicht - das ergibt einen halben Punkt Abzug!"
            except:
                return -1, ""
funktioniert auch ... wie aber bekomme ich die Einheit z.B. "m" oder "cm²". Damit würde ich gerne eine Rückmeldung geben, das z.B. die Längeneinheit mit der Flächeneinheit verwechselt wurde.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Und schon wieder ein nackte except, das die Fehlersuche unmöglich macht. Variablennamen sollten nicht kryptische Abkürzungen enthalten. Wenn Du loesung meinst, warum schreibst Du sinn nur los?
findall ist falsch, wenn man nur eine Zahl erwartet. Deinen regulären Ausdruck musst du doch nur um das Pattern für die Einheit erweitern.
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Sirius3 hat geschrieben: Sonntag 20. November 2022, 16:17 Und schon wieder ein nackte except, das die Fehlersuche unmöglich macht.
Hier ging es mir doch gar nicht um Fehlersuche - ich weiß nicht so recht, was ich sonst noch an Code einfügen soll/kann.
Sirius3 hat geschrieben: Sonntag 20. November 2022, 16:17 Variablennamen sollten nicht kryptische Abkürzungen enthalten. Wenn Du loesung meinst, warum schreibst Du sinn nur los?
Ok, geht es nur um "loesung" statt "loe" oder was meinst du mit "... warum schreibst Du sinn nur los"?
Sirius3 hat geschrieben: Sonntag 20. November 2022, 16:17 findall ist falsch, wenn man nur eine Zahl erwartet.
wie ändere ich das?
Sirius3 hat geschrieben: Sonntag 20. November 2022, 16:17 Deinen regulären Ausdruck musst du doch nur um das Pattern für die Einheit erweitern.
wie? (ich fühle mich auch von den regulären Ausdrücken etwas überfordert)
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast dir mit dem nackten try/except ein gefaehrliches Muster angewoehnt, dass dir auch schon an anderer Stelle auf die Fuesse gefallen ist. Lass das einfach. Wenn du Fehlerbehandlung machen willst, musst du *immer* etwas der Art

Code: Alles auswählen

try:
    irgendwas = tuwas()
except EineKonkreteAusnahme, OderMehrere:
   ...
machen.

Was die regulaeren Ausdruecke angeht: das ist ein komplexes Thema. Dem kannst du dich versuchen mit Seiten wie https://regexr.com/ zB versuchen zu naehern. Hier mal ein kleines Beispiel, dass ich dir gebaut habe:

https://regexr.com/72q3t

Achtung, die Einheiten-Angabe ist etwas subtil. Aus technischen gruenden (greedy vs non greedy matching) muss das mm vor dem m kommen. Kann man in Python auch anders loesen (immer versuchen greedy zu matchen).

Alternativ statt solcher konkreter Einheiten kannst du auch einen (\w*) (Wort-Zeichen, 0-nmal) verwenden, und die erst in einem zweiten Schritt validieren. Das ist ggf. einfacher.
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Uih, das schafft mein armer alter Kopf nicht!
Wie findest du das:

Code: Alles auswählen

            loesung_getrennt=loesung.split()
            x_liste = ["c","d","m"]
            for x in x_liste:
                if x in eingabe:
                    eingabe_getrennt=eingabe.split(x)
                    eingabe_getrennt[1] = x + eingabe_getrennt[1]
?
(Ich habe in meiner Lösung ein Leerzeichen zwischen Zahl und Einheit eingefügt)

Nachtrag: Das klappt noch nicht ganz, aus "10mm²" wird "['10', 'm', '²']" - da muss ich noch drüber hirnen! (Das verstehe ich noch nicht)

Jetzt geht es (aber sicher nicht wirklich elegant):

Code: Alles auswählen

            loesung_getrennt=loesung.split()
            if "c" in eingabe:
                eingabe_getrennt=eingabe.split("c")
                eingabe_getrennt[1] = "c"+ eingabe_getrennt[1]
            elif "d" in eingabe:
                eingabe_getrennt=eingabe.split("d")
                eingabe_getrennt[1] = "d"+ eingabe_getrennt[1] 
            elif "m" in eingabe:
                eingabe_getrennt=eingabe.split("m")
                eingabe_getrennt[1] = "m"+ eingabe_getrennt[1] 
            eingabe_getrennt[0]=eingabe_getrennt[0].replace(",",".") 
 
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das problem ist die Eingabe des Leerzeichens. Das ist eine unangenehme Randbedingung. Wenn dir regex Zuviel ist, kannst du auch mit zb isdigit deine Zeichenkette zerteilen. Also alles, was digit ist, in ein Töpfchen, und den Rest danach in ein anderes.
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Wie wäre es, die Benutzer um Eingabe in zwei Feldern zu bitten, einmal die Zahl, einmal die Einheit?
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.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich würde hier ganz einfach die Zahl vom ganzen Rest trennen:

Code: Alles auswählen

def parse_number_with_unit(text):
    match = re.fullmatch('\s*([-+]\d+[.,]\d*)\s*(.*)', text)
    if not match:
        raise ValueError(text)
    number, unit = match.groups()
    return float(number.replace(',', '.')), unit
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Code: Alles auswählen

            if "c" in eingabe:
                eingabe_getrennt=eingabe.split("c")
                eingabe_getrennt[1] = "c"+ eingabe_getrennt[1]
            elif "d" in eingabe:
                eingabe_getrennt=eingabe.split("d")
                eingabe_getrennt[1] = "d"+ eingabe_getrennt[1] 
            elif "m" in eingabe:
                eingabe_getrennt=eingabe.split("m")
                eingabe_getrennt[1] = "m"+ eingabe_getrennt[1] 
... sieht ja irgendwie blöd aus. Ich müsste die Schleife dazu bringen nach dem ersten Fund abzubrechen, da bräuchte es ein "exit for":

Code: Alles auswählen

            x_liste = ["c","d","m"]
            for x in x_liste:
                if x in eingabe:
                    eingabe_getrennt=eingabe.split(x)
                    eingabe_getrennt[1] = x + eingabe_getrennt[1]
                    exit for
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

??? - welche Schleife? Die if-elif Kaskade wird nach dem 1. Treffer und der Ausführung des zugehörigen Befehlsblocks doch verlassen.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: ``EXIT FOR`` heisst in Python ``break``.

`x` und `x_liste` sind keine guten Namen. Grundatentypen sollten nicht in Namen stehen, das ändert sich gerne mal während der Entwicklung und dann hat man entweder irreführende, falsche Namen im Quelltext, oder muss den durchgehen und überall betroffene Namen ändern. Statt ``["c", "d", "m"]`` könnte da ja auch ``("c", "d", "m")`` stehen, oder einfach ``"cdm"``.

Das mit `split()` und dann das Trennzeichen wieder an einen Teil anfügen ist etwas kompliziert IMHO. Ich fände es ”natürlicher” wenn man den Index des Buchstabens sucht und dann per slicing die beiden Teile erstellt. Was man auch noch behandeln muss ist der Fall das es nichts zum trennen gibt. Da könnte man für die Einheit beispielsweise einfach die leere Zeichenkette annehmen. Ungetestet:

Code: Alles auswählen

            for buchstabe in "cdm":
                try:
                    index = eingabe.index(buchstabe)
                except IndexError:
                    pass
                else:
                    wert_text, einheit = eingabe[:index], eingabe[index:]
                    break
            else:
                wert_text, einheit = eingabe, ""
Wobei das Vorgehen an sich nicht wirklich robust ist, weil das davon ausgeht, das es niemals Einheiten geben kann, in denen die getesteten Buchstaben nicht als erstes Zeichen nach der Zahl stehen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

noisefloor hat geschrieben: Montag 21. November 2022, 16:35 ??? - welche Schleife? Die if-elif Kaskade wird nach dem 1. Treffer und der Ausführung des zugehörigen Befehlsblocks doch verlassen.
Die Schleife steht untendrunter (daher der Doppelpunkt).
__blackjack__ hat geschrieben: Montag 21. November 2022, 17:32 @Pitwheazle: ``EXIT FOR`` heisst in Python ``break``.
Das habe ich so schon ausprobiert.

Code: Alles auswählen

            for x in x_liste:
                if x in eingabe:
                    eingabe_getrennt=eingabe.split(x)
                    eingabe_getrennt[1] = x + eingabe_getrennt[1]
                    break
            print(eingabe_getrennt)        
... dabei bin ich einfach mal davon ausgegangen, dass die Schleife nach dem ersten Fund verlassen wird. "Print" ergibt bei Eingabe von "22mm²" aber wieder "['22', 'm', '²']". Das hatte ich ja oben auch schon mal so. Oder kann man das "break" so nicht anwenden?

__blackjack__ hat geschrieben: Montag 21. November 2022, 17:32 Wobei das Vorgehen an sich nicht wirklich robust ist, weil das davon ausgeht, das es niemals Einheiten geben kann, in denen die getesteten Buchstaben nicht als erstes Zeichen nach der Zahl stehen.
Nun ich weiß ja, dass keine anderen Einheiten vorkommen können. Zunächst überprüfe ich, ob in der Einagbe überhaupt eine Einheit eingegeben wurde, falls nicht erfolgt ein Hinweis und es kann die Eingabe entsprechend ergänzt werden. Dann überprüfe ich, ob die Eingabe meiner vorgegebenen Lösung entspricht, falls nicht, überprüfe ich, ob die Zahl richtig ist und gebe für diese richtige Zahl einen halben Punkt. Falls eine vollkommen andere Eingabe erfolgt, gehe ich davon aus, dass die Eingabe falsch ist - auch wenn z.B. km eingegeben wurde, damit habe ich jetzt kein Problem, mir geht es hauptsächlich darum, dass die Kids Längen und Flächeneinhaietn richtig zuordnen:

Code: Alles auswählen

            try:
                if float(eingabe_getrennt[0]) == float(loesung_getrennt[0]):
                    return 0.5, "Die Zahl stimmt, die Einheit aber nicht - das ergibt einen halben Punkt Abzug! Richtig wäre: " + loesung_getrennt[1]
            except:
                return -1, ""
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Nun ich weiß ja, dass keine anderen Einheiten vorkommen können.
Wie denn? Du hast doch ein Textfeld, d.h. da kann man grundsätzlich alles angeben. Also z.B. auch "zzz" als Einheit, obwohl eine Flächeneinheit gesucht ist. Oder hast du clientseitig schon eine Eingabevalidierung via JavaScript?

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Das ``break`` verlässt die Schleife beim ersten Fund. Das Problem mit "22mm²" ist ein anderes, was eigentlich offensichtlich ist. Was erwartest Du was "22mm²".split("m") als Ergebnis liefert? Probier das einfach mal in einer interaktiven Python-Shell aus. Ein weiterer Grund warum `split()` hier nicht wirklich die Wahl sein sollte.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

@noisefloor: OK, ich hätte genauer sagen müssen, "ich weiß, welche Einheiten in richtigen Antworten vorkommen können". Ich hatte ja auch beschrieben, dass ich anstelle von "22 cm²" "22 Käsekuchen" nicht mit einem Teilpunkt bewerten werde.

@__blackjack__:

Code: Alles auswählen

                    eingabe_getrennt=eingabe.split(x,1)
                    eingabe_getrennt[1] = x + eingabe_getrennt[1]
                    break
... funktioniert jetzt aber so, wie ich es mir vorgestellt habe. Ich glaube das lasse ich jetzt so.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@Pitwheazle: die bessere Lösung wäre es, mit regulären Ausdrücken zu arbeiten, anstatt selbst einen Parser zu schreiben. In der Zeit, in der Du Dir hier diese komplizierte Spit-Logik zusammengeschreiben hast, wäre auch ein Einarbeiten in reguläre Ausdrücke möglich gewesen, und Du hättest noch was für später gelernt gehapt.
Antworten