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'
Hier mal das gesamte Skript.