Eleganter Prüfen ob String Bedingung erfüllt?

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
Bernhard
User
Beiträge: 136
Registriert: Sonntag 15. Januar 2006, 20:31
Wohnort: Greifswald
Kontaktdaten:

Hallo Leute,

im echten Leben habe ich über 100 Fragebögen auf denen je 30 Fragen mit Antworten zwischen 0 und 4 beantwortet wurden. Den ganzen Kram muss ich jetzt in den Rechner eingeben. Mein Plan ist, einfach über raw_input() die Antworten als Folge von 30 Ziffern einzulesen. Eine valide Eingabe wäre etwa

'000102004030341020102201011240'

(erste drei Fragen wurden mit '0' beantwortet, die vierte mit '1' usw.)

bevor ich das verarbeite muss ich prüfen, ob wirklich 30 Ziffern eingegegen wurden und ob die alle zwischen 0 und 4 liegen. Die nachfolgende Auswertung soll ja nicht scheitern, nur weil man versehentlich auf eine Buchstabentaste gekommen ist oder bei der Zifferneingabe ein Shift gedrückt war. Zur Zeit mache ich das so:

Code: Alles auswählen

    def teste_itemstring(self, itemstring):
        "Testet einen Itemstring auf syntaktische Korrektheit."
        itemstring=itemstring.strip()
        if len(itemstring)==30 and itemstring.isdigit():
            for i in xrange(0,30):
                if not (0 <= int(itemstring[i]) <= 4):
                    return(False)
            return(True)
        return(False)
Im nächsten Schritt wandle ich diesen String dann in eine Liste der Art
[0,0,0,1,0,2,...] zu.

Frage 1: Kann man diese Überprüfung vielleicht eleganter/pythonischer als mit dieser if-for-if-Konstruktion machen, beispielsweise mit Regulären Ausdrücken oder so? (Performanz spielt keine Rolle, Lesbarkeit und Schönheit schon)

Frage 2: Kann ich die Prüfung und Umwandlung in eine Liste etwas benutzerfreundlicher machen? Das Erfassen der Bögen wird eine Sch...arbeit und z. B. Zeile 3 dient dazu, dass ein Itemstring auch dann verstanden wird, wenn der User versehentlich ein Space am Anfang oder Ende gedrückt hat. Ich würde ihm gerne auch mehr erlauben. Cool wäre, wenn außer
(a) '000102004030341020100001011240'
auch
(b) '0001020040 3034102010 0001011240'
oder
(c) '0001020040.3034102010.0001011240'
oder sogar auch
(d) '(3*0)1020040303410201(4*0)1011240'
korrekt gelesen würden.

Vielen Dank,
Bernhard
Zuletzt geändert von Bernhard am Freitag 24. April 2009, 13:32, insgesamt 1-mal geändert.
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Moin,
Bernhard hat geschrieben:Frage 1: Kann man diese Überprüfung vielleicht eleganter/pythonischer als mit dieser if-for-if-Konstruktion machen, beispielsweise mit Regulären Ausdrücken oder so? (Performanz spielt keine Rolle, Lesbarkeit und Schönheit schon)

Code: Alles auswählen

if re.match("^[0-4]+$", "000102004030341020102201011240"):
    ...
Frage 2: Kann ich die Prüfung und Umwandlung in eine Liste etwas benutzerfreundlicher machen?

Code: Alles auswählen

if re.findall("[0-4]", "  0001020 0403034 102010220101 1240  "):
    ...
Für die Geschichte mit "(3*0)" müsstest du einen eigenen Parser schreiben.

Gruß,
Manuel[/code]
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Da kann man Mengen fuer nehmen:

Code: Alles auswählen

>>> s = '000102004030341020102201011240' 
>>> values = set("01234")
>>> len(s) == 30 and set(s) <= values
True
EDIT: OK, ich habe die Aufgabenstellung nicht richtig gelesen...
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Da ich kein Freund von RE bin, hätte ich es z.B. so gemacht:

[falschen Code entfernt - unten richtig]

Das mit dem "(3*0)" etc. lohnt sich doch nicht wirklich!
Erst ab 6 aufeinanderfolgenden gleichen Antworten spart man dabei Zeichen ein und Eingabezeit sparst du vermutlich erst ein, wenn die Zahl der Wiederholungen noch höher ist.

Und, wie schon von helduel angemerkt: Das musst du parsen. Und auch da wieder auf Fehler prüfen und die ausbügeln. Der Aufwand lohnt sich IMHO dafür nicht.

Edit: Korrektur - habe die 30 vergessen:

Code: Alles auswählen

def result(s):
    s = s.replace(" ","").replace(".","")
    if sum([s.count(answer) for answer in '01234']) == len(s) == 30:
        return map(int,list(s))
    return "Fehlerhafte Zeichen"

print result('0001  020040303410  20.10220..1011240')
Rebeccas Idee mit der Menge ist auch gut:

Code: Alles auswählen

def result(s):
    s = s.replace(" ","").replace(".","")
    if set("01234") == set(s) and len(s) == 30:
        return map(int,list(s))
    return "Fehlerhafte Zeichen"

print result('0001  020040303410  20.10220..1011240')
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Bernhard:
Wie wärs, die Eingabe mit Python vorzunehmen und gleich auf den zulässigen Wertebereich zu prüfen? Sind die Fragen auf den Bögen nummeriert, kannst Du dem Eingebenden auch noch die Fragenummer präsentieren und so evtl. die Fehlerrate minimieren.

Die Sache mit (3*0) sehe ich ähnlich wie nummerix, ich denke auch, das sowas eher Tippfehler provoziert, wo der Eingebende doch sonst "nur" [0-4] auf dem Numblock bedienen muß (was auch für Ungeübte blind geht).
Benutzeravatar
hendrikS
User
Beiträge: 420
Registriert: Mittwoch 24. Dezember 2008, 22:44
Wohnort: Leipzig

numerix hat geschrieben:Da ich kein Freund von RE bin, ...:
Ich finde gut, dass mal jemand darauf aufmerksam macht, dass man triviale Probleme auch mit der Standard Lib lösen kann.
Bernhard
User
Beiträge: 136
Registriert: Sonntag 15. Januar 2006, 20:31
Wohnort: Greifswald
Kontaktdaten:

Hallo Leute,

Ihr seid ja superschnell! Danke für die Denkanregungen. Völlig klar, das mit dem Parsen lohnt sich nicht. In enger Anlehnung an Manuel werde ich also mit

Code: Alles auswählen

re.match("^[0-4,\s]+$", itemstring)
prüfen, ob nur die genannten Ziffern und Spaces enthalten sind,
dann mit

Code: Alles auswählen

len( re.findall("[0-4]", itemstring) ) == 30


prüfen, ob die richtige Zahl Ziffern eingegeben wurden.
Mädels und Jungs, Ihr seid richtig gut und wahnsinnig schnell.

Vielen Dank!
Bernhard


Edit: Ihr postet ja schneller als ich meine Antwort tippen kann. Die Rebecca/Nummerix-Lösung gefällt mir auch sehr gut. Werde eine von beiden umsetzen. Noch mal vielen, vielen Dank.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Bernhard hat geschrieben:

Code: Alles auswählen

len( re.findall("[0-4]", itemstring) ) == 30


prüfen, ob die richtige Zahl Ziffern eingegeben wurden.
Mädels und Jungs, Ihr seid richtig gut und wahnsinnig schnell.
Man könnte auch ``re.match("^[0-4]{30}$", itemstring)`` nutzen, das prüft dann auch gleichzeitig die Länge der Eingabe.

Meine eigene Lösung sähe hingegen so aus:

Code: Alles auswählen

from itertools import imap
from operator import contains
from functools import partial

res = '000102004030341020102201011240'
print len(res) == 30 and all(imap(partial(contains, '01234'), res))
Funktionsapplikation ftw! :)

Edit: DasIch hat recht, falschen Quantor benutzt.
Zuletzt geändert von Leonidas am Freitag 24. April 2009, 16:52, insgesamt 1-mal geändert.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Ich würde ja überprüfen ob alle Zahlen entweder 0, 1, 2, 3 oder 4 sind ;)
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Numerix: Du benutzt oft LC's wo sie nicht notwendig sind, wie hier bei sum(). Oft ist es aber besser, einen Generator Ausdruck zu benutzen. Dann hat man weniger Arbeitsspeicheraufwand, da ja nicht zuerst eine Liste konstruiert werden muss. Generatorausdrücke unterscheiden sich in der Geschwindigkeit nur unwesentlich von LCs. Mit einer LC kann man aber nicht alle Fälle abdecken, die man mit einem Generator abdecken könnte, und vorallem nicht Speicher / Perfomance sparend. Ist mir bei deinem Code schon öfter aufgefallen.
Bernhard
User
Beiträge: 136
Registriert: Sonntag 15. Januar 2006, 20:31
Wohnort: Greifswald
Kontaktdaten:

Leonidas hat geschrieben:Man könnte auch ``re.match("^[0-4]{30}$", itemstring)`` nutzen, das prüft dann auch gleichzeitig die Länge der Eingabe.
..aber dann dürfte man bei der Eingabe ja wieder keine Whitespaces benutzen um sich die Zahlenkolonne übersichtlicher zu machen (mein Beispiel (b) ). Außerdem kann ich so die Liste weiter nutzen die re.findall zurück gibt.

Deine Lösung ist bestimmt die elegantere, aber für mich ahnungslosen Gelegenheitsprogrammierer ist sie halt kaum lesbar...

Danke Euch allen,
Bernhard
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Bernhard hat geschrieben:aber dann dürfte man bei der Eingabe ja wieder keine Whitespaces benutzen um sich die Zahlenkolonne übersichtlicher zu machen (mein Beispiel (b) ). Außerdem kann ich so die Liste weiter nutzen die re.findall zurück gibt.
Hast recht, aber man könnte vorher die Whitespaces rausfiltern, das wäre auch nicht sonderlich komplizierter.

Sieht dann etwas so aus, was so gesehen auch noch etwas einfacher ist:

Code: Alles auswählen

len(filter(partial(contains, '01234'), res)) == 30
Wobei du aber noch sagen müsstest, ob es möglich ist dass du mehr als 30 Antworten gibt, in dem Fall tut obiger Code nicht richtig.

Edit: Und nun noch etwas Obfuscated Functional Python:

Code: Alles auswählen

valid = lambda z: (lambda x, y: len(x) == len(y) == 30)(*(filter(partial(contains, '01234'), z), filter(partial(contains, digits), z)))
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

str1442 hat geschrieben:Numerix: Du benutzt oft LC's wo sie nicht notwendig sind, wie hier bei sum(). Oft ist es aber besser, einen Generator Ausdruck zu benutzen. Dann hat man weniger Arbeitsspeicheraufwand, da ja nicht zuerst eine Liste konstruiert werden muss. Generatorausdrücke unterscheiden sich in der Geschwindigkeit nur unwesentlich von LCs. Mit einer LC kann man aber nicht alle Fälle abdecken, die man mit einem Generator abdecken könnte, und vorallem nicht Speicher / Perfomance sparend. Ist mir bei deinem Code schon öfter aufgefallen.
Ja, stimmt alles. Ich mag Generatoren nicht so sehr und benutze sie meist nur, wenn die ansonsten benötigte Datenstruktur wirklich viel Speicher frisst. Ist sicher bei dem obigen Beispiel nicht der Fall, aber natürlich hätte ich trotzdem die eckigen Klammern weglassen können ...
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Code: Alles auswählen

import re

s = "0(3*1)(2*2)0"

s = re.sub(r"\((\d+)\*(\d)\)", lambda m: int(m.group(1)) * m.group(2), s)

if not re.match(r"[0-4]{30}", s):
    print s, "hat nicht das richtige Format"
Stefan
Bernhard
User
Beiträge: 136
Registriert: Sonntag 15. Januar 2006, 20:31
Wohnort: Greifswald
Kontaktdaten:

And the winner is... "sma"!

Ein Parser in einer Zeile? Ihr bestätigt alle meine Vorurteile gegenüber Python und Euch Profis hier im Forum. Glücklicherweise liegen mit zu jedem Bogen bereits drei von Hand berechnete Prüfsummen vor, so dass ich beim Eintippen immer eine ziemlich gute Rückmeldung erhalte.
Und: Nein, die Zahl der Items wird sich nicht ändern, weil dieser Fragebogen weltweit etabliert und seine Deutsche Übersetzung seit Jahren normiert ist (ist für Euch uninteressant, aber es ist dieser hier:
http://www.dgpp.de/Profi/Sources/VHI-Bo ... 2003-2.pdf

Gruß an alle,
Bernhard
Antworten