Code:
#!/usr/bin/env python2
import urllib2
import re
######################################################################################################################
######################################################################################################################
### Klasse CompetitorTimes => Teilnehmer und Verarbeitung derer Zeiten einer Disziplin
### Klassenattribute:
### - TimeFormate MEAN, AVG, BEST
### Attribute:
### - competitor String : Name Teilnehmer
### - time_format Integer : -> Klassenattribute
### - times Double-Liste : Zeiten des Teilnehmers
### - times_count Integer : Zeitenanzahl
### - best Double : Bestzeit
### - times_format_best Double : Bestzeit im Timeformat
##########
### Klassenmethoden:
### - msToTime(ms) String : Zeit im Format ms in min:sec.ms umwandeln
### - timeToMS(Time) Integer : Zeit im Format min:sec.ms in ms umwandeln
### - compare_competitors() Integer : Anwendung von sort()
### Objektmethoden:
### - __init__() CompetitorTimes : Konstruktur
### - __str__() String : Stringrepraesentation von Competitor in der Form "Name AvgTime BestTime"
### - calcTimesFormated() Double : Berechnung der Zeit im time_format
### - getBest() Double
### - getCompetitor() String
### - getTime() Double
### - getTimesCount() Integer
######################################################################################################################
######################################################################################################################
class CompetitorTimes(object):
MEAN = 0
AVG = 1
BEST = 2
def __init__(self, competitor, time_format, times):
self.competitor = competitor
self.time_format = time_format
self.times = times
self.times_count = len(self.times)
self.best = self.calcTimesFormated(CompetitorTimes.BEST)
self.time_format_best = self.calcTimesFormated(self.time_format)
def __str__(self):
if self.time_format == CompetitorTimes.BEST:
return '%s %s' % ( CompetitorTimes.msToTime(self.getTime()), self.getCompetitor())
else:
return '%s %s %s' % (CompetitorTimes.msToTime(self.getTime()), CompetitorTimes.msToTime(self.getBest()), self.getCompetitor())
@staticmethod
def compare_competitors(a, b):
''' Funktion um sort() auf CompetitorTimes-Liste anwenden zu koennen
DNF => -1
achte auf -1 Averages und -1 BestTimes
Falls zwei zeitgleiche, waehle Bestzeit
Falls diese auch gleich, waehle fruehren Poster'''
atime = a.getTime()
btime = b.getTime()
if atime == btime:
if a.getBest() == -1 and b.getBest() == -1:
return 0
elif a.getBest() == -1:
return 1
elif b.getBest() == -1:
return -1
else:
return int((a.getBest() - b.getBest())*1000)
elif atime == -1:
return 1
elif btime == -1:
return -1
else:
return int((atime - btime)*1000)
@staticmethod
def msToTime(ms):
ms = int(ms)
minuten = ms / (1000*60)
sekunden = (ms / 1000) - minuten*60
ms %= 1000
msstr = str(ms)
if ms < 10:
msstr = '00' + msstr
elif ms > 10 and ms < 100:
msstr = '0' + msstr
sekundenstr = str(sekunden)
if sekunden < 10:
sekundenstr = "0" + sekundenstr
if minuten == 0:
return sekundenstr + "." + msstr[:2]
return str(minuten) + ":" + sekundenstr + "." + msstr[:2]
@staticmethod
def timeToMS(time):
if time == '-1':
return -1
tmp = time.split(':')
time = 0
if len(str(tmp[-1])) > 2:
tmp[-1] = int(str(tmp[-1])[:-1])
try:
time += int(tmp[-2])*1000 + int(tmp[-1])*10
except:
print tmp
if len(tmp) > 2:
time += int(tmp[-3])*1000*60
if len(tmp) > 3:
time += int(tmp[-4])*1000*60*60
return time
def calcTimesFormated(self, time_format):
''' Berechne:
- Average:
Durchschnitt ohne beste und schlechteste Zeit
DNF-Average bei >= 2 DNFs
- Mean:
Durchschnitt aller Zeiten
DNF-Mean bei >= 1 DNF
- Beszeit:
Beste Zeit
DNF-BEST wenn alle Einzelzeiten DNF'''
if time_format == CompetitorTimes.BEST:
minimum = self.times[0]
for i in xrange(1, self.times_count):
if minimum == -1 or (minimum > self.times[i] and self.times[i] != -1):
minimum = self.times[i]
return minimum
dnfs = sum(1 for time in self.times if time == -1)
if dnfs == 0:
summe = minimum = maximum = self.times[0]
for i in xrange(1, self.times_count):
minimum = min(minimum, self.times[i])
maximum = max(maximum, self.times[i])
summe += self.times[i]
if time_format == CompetitorTimes.AVG:
return (summe-maximum-minimum)*1.0 / (self.times_count-2)
elif time_format == CompetitorTimes.MEAN:
return summe*1.0 / self.times_count
elif dnfs == 1:
if self.times_count == 1 or time_format == CompetitorTimes.MEAN:
return -1
elif time_format == CompetitorTimes.AVG:
summe = minimum = self.times[0]
if self.times[0] == -1:
minimum = self.times[1]
summe = 0
for i in xrange(1, self.times_count):
if self.times[i] != -1:
minimum = min(minimum, self.times[i])
summe += self.times[i]
return (summe-minimum)*1.0 / (self.times_count-2)
else:
return -1
def getBest(self):
return self.best
def getCompetitor(self):
return self.competitor
def getTime(self):
return self.time_format_best
def getTimesCount(self):
return self.times_count
######################################################################################################################
######################################################################################################################
### Klasse ForumThread => Thread einlesen
### Klassenattribute:
### - LINK String : Link zu printpages
### - PAGE String : um Pageid zu aendern
### Attribute:
### - tid Integer : Threadid => um Link zu generieren
### - pid Integer : Pageid => um Linz zu generieren
### - link String : vollstaendiger Link
### - fulltext String : gesamter Thread
### - maxPage Integer : Seitenanzahl des Threads
### - post Post-Liste : Postliste mit Postobjekten
### - post_count Integer : Anzahl der Posts
##########
### Objektmethoden:
### - readPage() String : liest eine Seite eines Threads
### - readPost() void : fuegt Post(author, post) in self.post hinzu
### - setMaxPage() Integer : zu lesende Seitenanzahl
### - getPost(i) Post : liefere i.ten Post zurueck
### - getPostCount() Integer
### - getTid() Integer
######################################################################################################################
######################################################################################################################
class ForumThread(object):
### Links
LINK = "http://forum.speedcubers.de/printthread.php?tid="
PAGE = "&page="
def __init__(self, tid):
self.tid = tid
self.pid = 1
self.link = ForumThread.LINK + str(self.tid)
self.fulltext = ''
self.readPage()
self.maxPage = self.setMaxPage()
while(self.pid < self.maxPage):
self.pid += 1
self.readPage()
print 'Saved %d Pages' % (self.pid)
self.post = []
self.post_count = 0
self.readPosts()
print 'Saved %d Posts' % (self.post_count)
def readPage(self):
url = self.link + ForumThread.PAGE + str(self.pid)
try:
self.fulltext += urllib2.urlopen(urllib2.Request(url)).read()
return True
except:
return False
def readPosts(self):
startPost = '<!-- start: printthread_post -->'
endPost = '<!-- end: printthread_post -->'
startPos = 0
endPos = 0
while(startPos != -1):
startPos = self.fulltext.find(startPost, endPos)
endPos = self.fulltext.find(endPost, startPos)
if(startPos != -1 and endPos != -1):
self.post.append(Post.parsePost(self.fulltext[startPos:endPos]))
self.post_count += 1
def setMaxPage(self):
startPos = self.fulltext.find('<div class="multipage">Seiten: <strong></strong>')
if startPos == -1:
return 1
else:
endPos = self.fulltext.find('</a> </div></td>\n', startPos)
return int(self.fulltext[endPos-1:endPos])
def getPost(self, i):
return self.post[i]
def getPostCount(self):
return self.post_count
def getTid(self):
return self.tid
######################################################################################################################
######################################################################################################################
### Klasse Post => Datenkapselung in der Threadklasse
### Klassenattribute:
### - RE_AUTHOR String : Regex zu Filterung des Postauthors
### - RE_TAGS String : Filterung von Forentags und [()+,] bei den Zeiten
### - RE_DNF String : erzetzt DNF mit -1
### - RE_TIMES String : Regex zum finden der Zeiten zu Disziplin.getName()
### Attribute:
### - author String : Autor des Posts
### - post String : Post
##########
### Klassenmethoden:
### - parsePost() Post : Name, Post filtern; Forentags entfernen, DNF -> -1
### #- filterTimes() Double-Liste : Filtert Zeiten zu gewisser Disziplin
### Objektmethoden:
### - getAuthor() String
### - getPost() String
######################################################################################################################
######################################################################################################################
class Post(object):
RE_AUTHOR = r'action=profile&uid=[0-9]+">(.*?)</a>'
RE_TAGS = r'<(.*?)>|\[(.*?)\]|[(),;+]'
RE_DNF = r'(?i)dnf'
RE_TIMES = r'[:]?\s+([-0-9a:.\t ]+)'
def __init__(self, author, post):
self.author = author
self.post = post
@staticmethod
def parsePost(post):
author = re.findall(Post.RE_AUTHOR, post)[0]
post = '\n'.join(post.split('\n')[4:-4]) # Filtert Header + Footer des Posts
post = re.sub(Post.RE_TAGS, '', post)
post = re.sub(Post.RE_DNF, '-1', post)
post = post.replace('[', '<')
post = post.replace(']', '>')
return Post(author, post)
@staticmethod
def filterTimes(disciplineName, post):
times = []
regex = disciplineName+Post.RE_TIMES
tmp = re.findall(regex, post)
if len(tmp) > 0:
tmp = re.sub('[:.]', ':', tmp[0]).strip()
tmp = re.sub('[ ]{2,}', ' ', tmp)
times = tmp.split(' ')
for i in xrange(len(times)):
times[i] = CompetitorTimes.timeToMS(times[i])
return times
def getAuthor(self):
return self.author
def getPost(self):
return self.post
######################################################################################################################
######################################################################################################################
### Klasse Discipline => Trennen der Disziplinen
### Attribute:
### - name String : Name der Disziplin
### - regex String : Regex zur Erkennung der Disziplin
### - time_format Integer : welches CompetitorTimes.time_format wird angewandt
### - competitors CompetitorTimes : Teilnehmer bei dieser Disziplin
### - scrambles Integer : Anzahl Scrambles
##########
### Objektmethoden:
### - __init__() Discipline : Konstruktur
### - __str__() String : Stringrepraesentation von Discipline zusammengesetzt aus Competitors
### - addCompetitor() void : Hinzufuegen eines Teilnehmers
### - sortCompetitor() void : Teilnehmer nach time_format sortieren
### - setRegex(regex) void
### - getCompetitorsCount() Integer : Anzahl an Teilnehmer
### - getName() String
### - getRegex() String
### - getTimeFormat() Integer
### - getScrambles() Integer
### - getWinner() String : Liefert Gewinner der Disziplin
######################################################################################################################
######################################################################################################################
class Discipline(object):
def __init__(self, name, time_format, scrambles):
self.name = name
self.regex = name
self.time_format = time_format
self.scrambles = scrambles
self.competitors = []
def __str__(self):
out = ''
for i in xrange(len(self.competitors)):
out += '%d. %s \n' % (i+1, str(self.competitors[i]))
return out
def addCompetitor(self, competitor):
self.competitors.append(competitor)
def sortCompetitors(self):
self.competitors.sort(CompetitorTimes.compare_competitors)
def setRegex(self, regex):
self.regex = regex
def getCompetitorsCount(self):
return len(self.competitors)
def getName(self):
return self.name
def getRegex(self):
return self.regex
def getScrambles(self):
return self.scrambles
def getTimeFormat(self):
return self.time_format
def getWinner(self):
self.sortCompetitors()
if self.getCompetitorsCount() > 0:
return str(self.competitors[0])
else:
return 'Keine Teilnehmer!'
### Main
if __name__ == "__main__":
print 'Initializing Dicsciplines'
disciplines = []
### Average of 12
disciplines.append( Discipline('2x2x2', CompetitorTimes.AVG, 12) )
disciplines.append( Discipline('Pyraminx', CompetitorTimes.AVG, 12) )
### Average of 5
disciplines.append( Discipline('3x3x3', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('3x3x3 OH', CompetitorTimes.AVG, 5) )
#disciplines[3].setRegex('3x3x3 One-Handed|3x3x3 OH')
disciplines.append( Discipline('4x4x4', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('5x5x5', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('Megaminx', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('Square-1', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('Clock', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('Magic', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('Master Magic', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('Mirror Blocks', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('2x2x3', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('2x2x4', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('4x4x6', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('3x3x3 feet', CompetitorTimes.AVG, 5) )
disciplines.append( Discipline('Mastermorphix', CompetitorTimes.AVG, 5) )
### Mean of 3
disciplines.append( Discipline('6x6x6', CompetitorTimes.MEAN, 3) )
disciplines.append( Discipline('7x7x7', CompetitorTimes.MEAN, 3) )
disciplines.append( Discipline('8x8x8', CompetitorTimes.MEAN, 3) )
### Best of 5
disciplines.append( Discipline('2x2x2 BLD', CompetitorTimes.BEST, 5) )
### Best of 3
disciplines.append( Discipline('3x3x3 BLD', CompetitorTimes.BEST, 3) )
disciplines.append( Discipline('4x4x4 BLD', CompetitorTimes.BEST, 3) )
disciplines.append( Discipline('5x5x5 BLD', CompetitorTimes.BEST, 3) )
### Best of 1
disciplines.append( Discipline('Gigaminx', CompetitorTimes.BEST, 1) )
disciplines.append( Discipline('2x2-4x4 Relay', CompetitorTimes.BEST, 1) )
disciplines.append( Discipline('2x2-5x5 Relay', CompetitorTimes.BEST, 1) )
### Manuelle Eingabe bei falschen Format:
# disciplines[x].addCompetitor(CompetitorTimes('Name', disciplines[x].getTimeFormat(), [1,2,3,4,5]))
print 'Reading Thread and parsing Posts'
wettbewerbsThread = ForumThread(9366)
# wettbewerbsThread = ForumThread(9229)
for i in xrange(wettbewerbsThread.getPostCount()):
post = wettbewerbsThread.getPost(i)
for discipline in disciplines:
times = Post.filterTimes(discipline.getRegex(), post.getPost())
if len(times) == discipline.getScrambles():
discipline.addCompetitor(CompetitorTimes(post.getAuthor(), discipline.getTimeFormat(), times))
elif len(times) != 0 and len(times) != discipline.getScrambles():
print 'Problem with user %s in discipline %s' % (post.getAuthor(), discipline.getName())
print times
print 'Start Calculation'
winners = ''
full_ranking = ''
for discipline in disciplines:
if discipline.getCompetitorsCount() > 0:
winners += '%s\t[%d Teilnehmer]: \t%s\n' % (discipline.getName(), discipline.getCompetitorsCount(), discipline.getWinner())
full_ranking += '%s: [spoiler]' % (discipline.getName())
full_ranking += '%s\n' % (str(discipline))
full_ranking += '[/spoiler]\n\n'
print 'Saving Results'
saveResults = open(str(wettbewerbsThread.getTid()), 'w')
saveResults.write(winners + '\n\n' + full_ranking)
saveResults.close()
print 'Finished'
Die Formatierung der Kommentare wurde leider, warum auch immer, vom Forum nicht so übernommen wie sie bei mir sind. Dennoch sollte es einigermaßen lesbar sein.
Folgende Probleme wurden bisher behoben:
- Sebastiens 3stelligen Nachkommaziffern
- Vergessen der führenden Nullen bei den Millisekunden => 13.07 wurde als 13.7 angezeigt
Folgende Probleme sind bekannt:
-
Todo:
- Erkennung von mehreren Formaten
verschiedene Formate (Click to View) - Multi-BLD Erkennung