String mit variabler Anzahl von Parameter-Paaren parsen

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
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Hi,

wie könnte man einen String in der Form:

Code: Alles auswählen

091027 2146 2159 2310 2343 10 Chinesen mit nem Kontrabaß ...
in date und timeobjects parsen? Ziel ist eine kleine Zeiterfassung, Tag ist in der ersten Spalte, die anderen Werte sollten Uhrzeitpaare sein (Kommen, Gehen).

Nach dem Ersten immer zwei Leerzeichen weiterzählen (auf eine Ziffer folgend) und dann abschneiden, splitten.

Tabs statt Leerzeichen verwenden, am ersten Tab abschneiden.
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Mhh, oder ein re.findall -> alle Zahlen als Element.
Zuletzt geändert von whaeva am Dienstag 27. Oktober 2009, 22:13, insgesamt 1-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

whaeva hat geschrieben:Hi,

wie könnte man einen String in der Form:

Code: Alles auswählen

091027 2146 2159 2310 2343 10 Chinesen mit nem Kontrabaß ...
in date und timeobjects parsen?
Die erste Frage muss hier immer lauten: Woher kommt der String? Kann man das Format des "Strings" beeinflussen? Und ganz wichtig: Sind die Anzahl an Elementen, deren Länge, die Riehenfolge usw. immer fix?
Ziel ist eine kleine Zeiterfassung, Tag ist in der ersten Spalte, die anderen Werte sollten Uhrzeitpaare sein (Kommen, Gehen).
Also 2x Kommen & Gehen?
Nach dem Ersten immer zwei Leerzeichen
Zwei Leerzeichen sehe ich da nicht!
weiterzählen (auf eine Ziffer folgend) und dann abschneiden, splitten.

Tabs statt Leerzeichen verwenden, am ersten Tab abschneiden.
Was genau soll das sein? Eine Lösung? Klingt wenig präzise...

Wenn das Format immer genau so aussieht, kann man das per split() sicherlich gut machen. Du kannst dann ja per Indizes auf die gewünschten Felder zugreifen und diese entsprechend parsen und umwandeln. Schwerer wird es sicherlich, sollten die Anzahl an "Kommen und Gehen"-Werten variabel sein.

Zudem ist die "10" von den Chinesen natürlich wenig optimal - ansonsten könnte man einfach per Regexp die vorderen Werte schon mal vom Kommentar splitten mit wenig Kosten.
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

String kann man beeinflussen. Soll einfach zu Tippen sein, deswegen so wenig Zeichen wie möglich.
Datum, Zeit-Paare, Notizen
War so mein erster Gedanke, komplizierter geht immer :-)

Kommen-Gehen sollte immer paarweise sein, es können auch mehrere Paare vorkommen (Mittagspause).
Wenn kein Paar da, ist wohl noch nicht Feierabend..

Nach dem ersten Leerzeichen (nach Datum) kommen auf ein Zeit-Paar zwei Leerzeichen. Allerdings wird man in Python nicht die Leerzeichen abzählen müssen.

Hier mal ein erster Ansatz:

Code: Alles auswählen

import sys
import time, datetime
import re

filename = sys.argv[1]
lineno = 0

with open(filename, 'r') as f:
  sys.stderr.write("file %s opened\n" % (filename))
  for line in f:
    lineno += 1
    sys.stderr.write("%03d: " % (lineno))
        
    if len(line.strip()) == 0:              # empty
      sys.stderr.write("(empty)\n")
      continue
    elif re.match("\s*#\s*", line):         # comment
      sys.stderr.write("(comment)\n")
      continue
    else:                                   # parse
      nums =  re.findall(r"(\d+)", line)    # numbers
      if(nums):
        print len(nums), nums
        print "Date:", nums[0]
    #~ sys.stderr.write("\n")
        
  sys.stderr.write("file %s closed" % (filename))
sys.stderr.write("done.\n")
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

PS: Wenn ich eine präzise Lösung hätte, würde ich ja hier nicht posten, oder? :-)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

whaeva hat geschrieben:String kann man beeinflussen. Soll einfach zu Tippen sein, deswegen so wenig Zeichen wie möglich.
Dann würde ich doch nicht mein eigenes Format "erfinden", sondern eine Standard-Syntax nutzen, wie etwa CSV oder JSON. Dafür gibts Parser und man hat schon mal wenig Ärger mit dem Parsen. Zudem würde ich gucken, wie man einen String einfach und eindeutig in ein Datumsformat bekommt. Es gibt ja strftime() - evtl. gibts da ja auch etwas inverses dazu.
Datum, Zeit-Paare, Notizen
War so mein erster Gedanke, komplizierter geht immer :-)

Kommen-Gehen sollte immer paarweise sein, es können auch mehrere Paare vorkommen (Mittagspause).
Wenn kein Paar da, ist wohl noch nicht Feierabend..
Siehste, das kann man ja nicht ahnen. Damit wird es schon mal wesentlich schwieriger, da man ja nicht auf die Indizes schließen kann...

Wie genau geht denn das Erfassen von statten? Sitzt da jemand in der "Stasi"like Zentrale und tippert diese Daten in eine Textdatei, wenn jemand Mittagspause macht? Oder soll es eine Eingabemaske geben? Oder aber wird das ganze per RFID oder Barcode gescannt? Oder soll das eine einfache Zeiterfassung für Dich selber sein?

Sollte man eine nette GUI dafür basteln, so könnte man das ganze auch über eine Datenbank realisieren. Wobei das Eingeben eines Datensatze selbst mit einer einfachen GUI sicherlich komfortabler ist, als in einer wie auch immer gearteten Textdatei etwas runter zu hacken. (Vermutlich sogar im SQLite-Manager vom FF)

Zumal mir noch eines unklar ist: Wie erkennt man denn eine "Person"? Ist ja wichtig für das "Gehen" ;-) (Oder eben auch das "Wiederkommen"...)
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Wenn du deinen String da durchjagst, ist der Rest schnell gemacht:

Code: Alles auswählen

>>> s = "091027 2146 2159 2310 2343 10 Chinesen mit nem Kontrabass ..."
>>> ws = s.split()
>>> zeiten = filter(lambda s:"0000"<=s<="2359",ws[1:])
>>> ws[1:] = zip(zeiten[::2],zeiten[1::2])+[" ".join(ws[len(zeiten):])]
>>> ws
['091027', ('2146', '2159'), ('2310', '2343'), '10 Chinesen mit nem Kontrabass ...']
Erstes Listenelement: Datum; letztes Listenelement: Text; dazwischen: Tupel mit Anfangs- und Endzeit

Edit: "<" durch "<=" ersetzt.
Zuletzt geändert von numerix am Mittwoch 28. Oktober 2009, 09:57, insgesamt 1-mal geändert.
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Ich tippe die 4 Zeit-Zeichen noch von Hand ein, im nächsten Schritt starte ich dann nur ./zeiterfassung.py k / g / n 10 Chinesen mit nem Kontrabaß ...

Aber jetzt erstmal Schlafpause, mit der Datumsrechnerei komme ich noch nicht ganz klar..

Code: Alles auswählen

import sys
import time, datetime
import re
#~ import str

filename = sys.argv[1]
lineno = 0
delimiter = "\t"

with open(filename, 'r') as f:
  sys.stderr.write("file %s opened\n" % (filename))
  for line in f:
    lineno += 1
    sys.stderr.write("%03d: " % (lineno))
        
    if len(line.strip()) == 0:              # empty
      sys.stderr.write("(empty)\n")
      continue
    elif re.match("\s*#\s*", line):         # comment
      sys.stderr.write("(comment)\n")
      continue
    else:                                   # parse
      line = line[:line.find(delimiter)]    # everything up to \t
      nums =  re.findall(r"(\d+)", line)    # numbers
      if(nums):
        pairs = (len(nums)-1)/2
        print "Date:", nums[0], 
        date = datetime.datetime.strptime(nums[0], "%y%m%d")
        print date
        daytime  = datetime.datetime(1,1,1)
        for pair in range(pairs):
          print "Time:", nums[1+pair], nums[1+pair+1],
          start = datetime.datetime.strptime(nums[1+pair], "%H%M")
          end = datetime.datetime.strptime(nums[1+pair+1], "%H%M")
          endold = end
          daytime += end-start
          print end-start
        print daytime
        print
    #~ sys.stderr.write("\n")
        
  sys.stderr.write("file %s closed\n" % (filename))
sys.stderr.write("done.\n")
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

ui, numerix' code muss ich mir erstmal genauer ansehen, bevor ich dazu was sagen kann, ausser: kurz!
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Hyperion hat geschrieben:Es gibt ja strftime() - evtl. gibts da ja auch etwas inverses dazu.
Das Inverse dazu heißt üblicherweise ``strptime``.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

@numerix: du kannst str.split() auch noch den 'maxsplit-Parameter mitgeben.

... str.split nimmt keine Keywords?
Gib "None" als Platzhalter, damit split(None, 5) wie split() trennt.

hth, Jörg
Wir haben schon 10% vom 21. Jahrhundert hinter uns!
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

b.esser-wisser hat geschrieben:@numerix: du kannst str.split() auch noch den 'maxsplit-Parameter mitgeben.
Ja, das ist mir bekannt, nur dass man es hier nicht gebrauchen kann ... :wink:
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Leonidas hat geschrieben:
Hyperion hat geschrieben:Es gibt ja strftime() - evtl. gibts da ja auch etwas inverses dazu.
Das Inverse dazu heißt üblicherweise ``strptime``.
Ok, wieder was gelernt! Wenn mir jetzt noch jemand das "p" erklären könnte? "f" hatte ich immer als "from" im Kopf - wofür kann da "p" stehen? Komme da im Moment auf nichts sinnvolles...
BlackJack

f → "format" und p → "parse".
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

BlackJack hat geschrieben:f → "format" und p → "parse".
Danke :-) Lag also am "from" ;-)
BlackJack

@Hyperion: Ist ein hübsches Beispiel dafür warum Abkürzungen in Bezeichnern keine so gute Idee sind. :-)
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Ein bisschen auf numerix' code aufgebaut. Vielleicht kann es jemand gebrauchen / verbessern.

Wie bekomme ich ein negatives datetime.timedelta ("Unterstunden") dazu, z.B. "-1:50" darzustellen, anstelle von "-1 day, 22:30:00" ?

Code: Alles auswählen

import sys
import time, datetime
import re
#~ import str

filename = sys.argv[1]
lineno = 0
delimiter = "\t"
year = 2009
day = 8 # hrs
dayno = 0

overtime = datetime.timedelta(0)

with open(filename, 'r') as f:
  sys.stderr.write("file %s opened\n" % (filename))
  for line in f:
    lineno += 1
    daytime = datetime.timedelta(0)
    pause = datetime.timedelta(0)
    sys.stderr.write("%03d: " % (lineno))
        
    if len(line.strip()) == 0:              # empty
      sys.stderr.write("(empty)\n")
      continue
    elif re.match("\s*#\s*", line):         # comment
      sys.stderr.write("(comment)\n")
      continue
    else:                                   # parse
      dayno += 1
      ws = line.split()
      zeiten = filter(lambda s:"0000"<=s<="2359",ws[1:])
      ws[1:] = zip(zeiten[::2],zeiten[1::2])+[" ".join(ws[len(zeiten):])]
      #~ print ws
      date = datetime.datetime.strptime(ws[0], "%d%m").replace(year).date()
      print date,
      i = 0
      while(1):
        i+=1
        if type(ws[i]) is tuple:
          if i>1:
            oldend = end
          start = datetime.datetime.strptime(ws[i][0], "%H%M")
          end = datetime.datetime.strptime(ws[i][1], "%H%M")
          
          daytime += end-start
          if i>1:
            pause += start-oldend
        else:
          break
    print "Tag:", dayno, "Tagessumme:", daytime, "Pause:", pause, "Überstunden:", daytime-datetime.timedelta(hours=day)
    overtime += daytime-datetime.timedelta(hours=day)
  sys.stderr.write("file %s closed\n" % (filename))
print "Überstunden: ", overtime
sys.stderr.write("done.\n")
Antworten