Seite 1 von 1
Eleganter Prüfen ob String Bedingung erfüllt?
Verfasst: Freitag 24. April 2009, 12:20
von Bernhard
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
Re: Eleganter Prüfen ob String Bedingung erfüllt?
Verfasst: Freitag 24. April 2009, 12:34
von helduel
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]
Verfasst: Freitag 24. April 2009, 12:46
von Rebecca
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...
Re: Eleganter Prüfen ob String Bedingung erfüllt?
Verfasst: Freitag 24. April 2009, 12:53
von numerix
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')
Verfasst: Freitag 24. April 2009, 13:02
von jerch
@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).
Re: Eleganter Prüfen ob String Bedingung erfüllt?
Verfasst: Freitag 24. April 2009, 13:06
von hendrikS
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.
Verfasst: Freitag 24. April 2009, 13:06
von Bernhard
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
prüfen, ob nur die genannten Ziffern und Spaces enthalten sind,
dann mit
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.
Verfasst: Freitag 24. April 2009, 15:38
von Leonidas
Bernhard hat geschrieben:
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.
Verfasst: Freitag 24. April 2009, 15:58
von DasIch
Ich würde ja überprüfen ob
alle Zahlen entweder 0, 1, 2, 3 oder 4 sind

Verfasst: Freitag 24. April 2009, 19:58
von str1442
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.
Verfasst: Freitag 24. April 2009, 20:32
von Bernhard
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
Verfasst: Freitag 24. April 2009, 23:14
von Leonidas
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:
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)))
Verfasst: Samstag 25. April 2009, 05:59
von numerix
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 ...
Verfasst: Samstag 25. April 2009, 08:55
von sma
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
Verfasst: Sonntag 26. April 2009, 18:44
von Bernhard
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