Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
[intern] Auswertungsskript
#1
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&amp;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.
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


Nachrichten in diesem Thema
[intern] Auswertungsskript - von sol1x - 29.05.2012, 19:53

Gehe zu: