Seite 1 von 1

String mit variabler Anzahl von Parameter-Paaren parsen

Verfasst: Dienstag 27. Oktober 2009, 21:52
von whaeva
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.

Verfasst: Dienstag 27. Oktober 2009, 22:01
von whaeva
Mhh, oder ein re.findall -> alle Zahlen als Element.

Re: String mit variabler Anzahl von Parameter-Paaren parsen

Verfasst: Dienstag 27. Oktober 2009, 22:03
von Hyperion
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.

Verfasst: Dienstag 27. Oktober 2009, 22:39
von whaeva
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")

Verfasst: Dienstag 27. Oktober 2009, 22:40
von whaeva
PS: Wenn ich eine präzise Lösung hätte, würde ich ja hier nicht posten, oder? :-)

Verfasst: Dienstag 27. Oktober 2009, 23:00
von Hyperion
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"...)

Verfasst: Dienstag 27. Oktober 2009, 23:00
von numerix
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.

Verfasst: Dienstag 27. Oktober 2009, 23:18
von whaeva
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")

Verfasst: Dienstag 27. Oktober 2009, 23:20
von whaeva
ui, numerix' code muss ich mir erstmal genauer ansehen, bevor ich dazu was sagen kann, ausser: kurz!

Verfasst: Mittwoch 28. Oktober 2009, 12:07
von Leonidas
Hyperion hat geschrieben:Es gibt ja strftime() - evtl. gibts da ja auch etwas inverses dazu.
Das Inverse dazu heißt üblicherweise ``strptime``.

Verfasst: Mittwoch 28. Oktober 2009, 17:38
von b.esser-wisser
@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

Verfasst: Mittwoch 28. Oktober 2009, 17:55
von numerix
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:

Verfasst: Mittwoch 28. Oktober 2009, 18:10
von Hyperion
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...

Verfasst: Mittwoch 28. Oktober 2009, 19:20
von BlackJack
f → "format" und p → "parse".

Verfasst: Mittwoch 28. Oktober 2009, 19:23
von Hyperion
BlackJack hat geschrieben:f → "format" und p → "parse".
Danke :-) Lag also am "from" ;-)

Verfasst: Mittwoch 28. Oktober 2009, 19:33
von BlackJack
@Hyperion: Ist ein hübsches Beispiel dafür warum Abkürzungen in Bezeichnern keine so gute Idee sind. :-)

Einfache Zeiterfassung mit Textdatei

Verfasst: Mittwoch 28. Oktober 2009, 22:36
von whaeva
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")