""" Enthaelt alle Datenstrukturen um die noetigen Hierarchien zur Beschreibung einer Sitzplatzverteilung aufzubauen """ __author__ = "Michael Stangl" __version__ = "$Revision: $" __date__ = "$Date: $" __copyright__ = "Copyright (c) 2005 Michael Stangl" __license__ = "Python" from itertools import chain from random import * import ast import configparser, os import copy import math from collections import defaultdict from functools import total_ordering # Strukturklassen: Person, Gruppe, Tisch class Person: """enthaelt alle Daten zu einer Person die noetig sind""" def __init__(self, Id, Vorname, Name, Titel=''): self.Id = Id self._Vorname = Vorname self._Name = Name self._Titel = Titel def __repr__(self): return ' Person %s: "%s %s %s"' % ( self.Id, self._Titel, self._Vorname, self._Name) @total_ordering class Gruppe: """enthalt alle Daten zu einer Gruppe""" Art = 'Gruppe' def __init__(self, Id, Personen=[]): self.Id = Id self._Personen = set() self._Anzahl = 0 self.PvonId = {} for P in Personen: self.Person_dazu( P ) def __repr__(self): result = '+ %s %s: Ids: ' % (self.Art, self.Id) for P in self._Personen: result = result + repr(P.Id) + ',' result = result + ' %s Person(en)' % (self._Anzahl) return result def Person_dazu(self, P): self._Personen.add(P) self._Anzahl = self._Anzahl + 1 self.PvonId[ P.Id ] = P def Person_weg(self, P): if P in self._Personen : self._Personen.remove(P) self._Anzahl = self._Anzahl - 1 else: raise KeyError("Person ist nicht enthalten") def teilen(self, NeueGroesse=0): if self._Anzahl == 1: raise IndexError("Gruppe ist nur noch einer") if NeueGroesse == 0: NeueGroesse = int(self._Anzahl/2) one = Gruppe( self.Id, self._Personen ) two = Gruppe( self.Id, self._Personen ) two._Personen.clear() for i in range(NeueGroesse): two._Personen.add(one._Personen.pop()) one._Anzahl = len(one._Personen) two._Anzahl = len(two._Personen) return [one,two] def PersonVonPid(self, SearchedId): return self.PvonId[SearchedId] def Anzahl(self): return self._Anzahl def __contains__(self, P): return P in self._Personen def __iter__(self): for P in self._Personen: yield(P) def __eq__(self,other): return self._Anzahl == other._Anzahl def __lt__(self,other): return self._Anzahl < other._Anzahl def __getattr__(self, attrname): if attrname == 'Anzahl': return self._Anzahl elif attrname == 'Id': return self.Id else: raise AttributeError(attrname) class Tisch(Gruppe): """enthalt alle Daten zu einem Tisch""" Art = 'Tisch' def __init__(self, Id, Plaetze=1, Koordinaten=(0,0), pids=[], Nachbarliste=[], Hof=0, Nummer=0 ): self._Plaetze = Plaetze self.Hof = Hof self.Nummer = Nummer self.X_Koordinate = Koordinaten[0] self.Y_Koordinate = Koordinaten[1] self.Nachbarn = set(Nachbarliste) Gruppe.__init__( self, Id, pids ) def istvoll(self, n=0): if ((self._Anzahl + n) == self._Plaetze ): return True else: return False def istleer(self): if (self._Anzahl == 0 ): return True else: return False def istNachbar(self,Tid): if Tid in self.Nachbarn: return True else: return False def freiePlaetze(self): return int( self._Plaetze - self._Anzahl ) def besetztePlaetze(self): return self._Anzahl def Person_dazu(self, P): if (self._Anzahl == self._Plaetze ): raise IndexError("Tisch voll") else: Gruppe.Person_dazu( self, P ) def Person_weg(self, P): if (self._Anzahl == 0 ): raise IndexError("Tisch schon leer") else: Gruppe.Person_weg( self, P ) def teilen(self): pass def __repr__(self): result = ( Gruppe.__repr__( self ) + ' fuer [%s], %s noch frei,' % (self._Plaetze, self._Plaetze - self._Anzahl) ) result = result + " Nachbarn:" for N in chain(self.Nachbarn): result = result + repr(N) return result def __getattr__(self, attrname): if attrname == 'Anzahl': return self._Anzahl if attrname == 'Plaetze': return self._Plaetze elif attrname == 'Id': return self.Id else: raise AttributeError(attrname) # Containerklassen Personen, Gruppen, Tische, Plaetze class Personen: """ein Pool mit mehreren Personen""" def __init__( self, Personen=[] ): self._Personen = set(Personen) self.PvonId = {} for P in Personen: self.PvonId[ P.Id ] = P def __repr__( self): result = '+ PersonenListe:\n' for P in self._Personen: result = result + repr(P) + '\n' return result def PersonVonPid( self, SearchedId ): return self.PvonId[SearchedId] def PersonAdd( self, Person ): self._Personen.add( Person ) Pid = Person.Id self.PvonId[ Pid ] = Person def ListeVonIds( self, PersonenIds ): PersonenListe = [] for Id in PersonenIds: PersonenListe.append( self.PersonVonPid( Id ) ) return PersonenListe class Gruppen: """Containerklasse fuer Gruppenzusammenstellung """ Art = 'Gruppen' def __init__( self, GruppenListe=[]): self.GruppenListe = [] self.PvonPid = {} self.GvonPid = {} self.NLeute = 0 for G in chain( GruppenListe ): self.Gruppe_dazu( G ) print("gruppen hinzugefuegt") def __iter__( self): sortiert = self.GruppenListe[:] sortiert.sort() for G in sortiert: yield(G) def __repr__( self): result = "-- "+self.Art+"aufstellung:\n" for G in chain( self.GruppenListe): result = result + repr(G)+ '\n' result = result + "Gesamtanzahl der Leute: " + repr(self.NLeute) + "\n" return result def vonId(self, SearchedId): for G in chain(self.GruppenListe): if ( int(G.Id) == SearchedId ): return G def PersonVonPid(self, SearchedId): return self.PvonPid[SearchedId] def GruppeVonPid(self, SearchedId): return self.GvonPid[SearchedId] def Gruppe_dazu(self, G): self.GruppenListe.append( G ) self.NLeute = self.NLeute + G._Anzahl for P in G: self.PvonPid[ P.Id ] = P self.GvonPid[ P.Id ] = G class Tische: """Containerklasse fuer Tischaufstellung """ Art = 'Tisch' def __init__( self, TischListe =[] ): self.TischListe = TischListe self.Groesster = 0 self.NStuehle = 0 self.IdGroesster = 0 # stelle den groessten Tisch fest # und zaehle die Plaetze for T in chain( TischListe ): self.NStuehle = self.NStuehle + T.Plaetze if (T.Plaetze > self.Groesster ): self.Groesster = T.Plaetze self.IdGroesster = T.Id def __repr__( self ): result = "-- "+self.Art+"aufstellung:\n" for T in chain(self.TischListe): result = result + repr(T)+ '\n' result = ( result + " Groesster Tisch: " + repr(self.IdGroesster) + "( " + repr(self.Groesster) + " )\n" ) return result def Tisch_dazu(self, T): self.NStuehle = self.NStuehle + T.Plaetze self.TischListe.append( T ) if (T.Plaetze > self.Groesster ): self.Groesster = T.Plaetze self.IdGroesster = T.Id def __iter__( self): sortiert = self.TischListe[:] sortiert.sort() for T in sortiert: yield(T) def laden( self, FileName ): ## print("-- Tische laden") config = configparser.ConfigParser() config.read( os.path.expanduser(FileName) ) for section in config.sections(): Tid = int(section) # ast.literal_eval statt eval: tische.ini kann eine hochgeladene # Fremddatei sein (Web-M1), da darf kein beliebiger Code laufen. # Erlaubt weiterhin Zahlen, Tupel und Listen wie zuvor. Nummer = ast.literal_eval(config.get( section, 'Nummer' )) nPl = ast.literal_eval(config.get( section, 'Plaetze' )) Koords = ast.literal_eval(config.get( section, 'Koordinaten' )) Nachbarn = ast.literal_eval(config.get( section, 'Nachbarliste' )) Hofid = ast.literal_eval(config.get( section, 'Hof' )) T = Tisch( Tid, Plaetze=nPl, Koordinaten=Koords, Nachbarliste=Nachbarn, Hof=Hofid, Nummer=Nummer ) self.Tisch_dazu( T ) def vonId(self, SearchedId): for T in chain(self.TischListe): if ( int(T.Id) == SearchedId ): return T def deepcopy(self): NeueTischliste = [] for T in chain( self.TischListe ): TNeu = copy.deepcopy( T ) NeueTischliste.append( TNeu ) NeueTische = Tische( NeueTischliste ) return NeueTische # Klasse fuer Information der Verteilung der Sitze class Plaetze: """Informationen ueber freie oder benachbarte Plaetze""" def __init__(self, Tische): # je Anzahl freier Plaetze ein Set von Tisch-Ids. defaultdict(set) # erspart die manuelle Vorbelegung und liefert fuer noch nie # gesehene Fuellstaende automatisch ein leeres Set. self.__TIDsNFreie = defaultdict(set) self.__Groesster = Tische.Groesster self.__Tische = Tische for T in Tische.TischListe: n_freie = T.freiePlaetze() self.__TIDsNFreie[n_freie].add(T.Id) def __repr__(self): result = "\n-- Tische mit n Freien Plaetzen:\n" for i in range(self.__Groesster+1): # .get statt [i], damit das Anzeigen keine leeren Eintraege # ins defaultdict schreibt result = result + " " +repr(i) + " :" + repr(self.__TIDsNFreie.get(i, set())) + "\n" return result def IdsnFreie(self, n_freie): """gibt ein Set mit allen Tischids zurueck, wo noch X freie Plaetze sind""" return self.__TIDsNFreie[n_freie] def nFreie(self, n_freie): """gibt einen Tisch zurueck, wo noch X freie Plaetze sind""" try: TischId = int(self.__TIDsNFreie[n_freie].pop()) # aber veraendere deswegen die Liste nicht self.__TIDsNFreie[n_freie].add(TischId) return self.__Tische.vonId(TischId) except KeyError: return None def mindestensNFreie(self, n_freie): """gibt einen Tisch zurueck, wo noch X oder mehr freie Plaetze sind""" ## print "-- Plaetze.mindestensNFreie", n_freie ## print "-- Plaetze.mindestensNFreie.Groesster", self.__Groesster N = n_freie if n_freie > self.__Groesster: return None while N < self.__Groesster+1: T = self.nFreie(N) if T is None: N = N + 1 else: return T return None def NachbarTische(self, n1, n2): """gibt zwei Nachbartische zurueck, wo zwei Gruppen hinpassen falls durch eine Gruppe ein Tisch voll werden wuerde, ist voll=1 falls beide Tisch gefuellt werden wuerden, ist voll=2 """ # zuerst versuchen einen Tisch fuer die groessere der beiden Gruppen zu finden # falls es nicht klappt, wirds auch keine Nachbarn geben... n = max([n1,n2]) T = self.mindestensNFreie( n ) if T is None: return [None,None] T1 = self.mindestensNFreie(n1) # davon nun die Nachbarn abklappern, ob ein Tisch dabei ist, # wo noch genuegend Plaetze frei sind n_Nachbar = n2 IdNachbarTisch = 0 while n_Nachbar < self.__Groesster+1: # ein Set bauen, das aus Tischids besteht, die sowohl Nachbarn sind, # als auch mindestens die gewuenschte Anzahl von Plaetzen frei hat NeueNachbarn = self.__TIDsNFreie[n_Nachbar] & T1.Nachbarn try: IdNachbarTisch = NeueNachbarn.pop() T2 = self.__Tische.vonId( IdNachbarTisch ) return [T1,T2] except KeyError: n_Nachbar = n_Nachbar + 1 return [None,None] def update(self, Tisch, n_freivorher): """aktualisiere die aktuelle Platzverteilung""" ## print "----------------" ## print "Plaetze.update" self.__TIDsNFreie[n_freivorher].remove(Tisch.Id) freiPlaetze = Tisch.freiePlaetze() self.__TIDsNFreie[freiPlaetze].add(Tisch.Id) ## print "freie Plaetze: ", freiPlaetze, "Tischid", Tisch.Id, "\n" # Strafliste. Was ist wie schlecht? class Strafliste: """enthaelt die Informationen zur Vergabe der Wertigkeit einer Sitzplatzverteilung""" def __init__(self, GruppenTrennen={}, NichtNachbar=-5, Allein=-20, TischWert={}): self.GruppenTrennen = GruppenTrennen self.NichtNachbar = NichtNachbar self.Allein = Allein self.TischWert = TischWert def __repr__(self): """Drucke alle Bestrafungen auf der Konsole aus""" return ("+ Strafliste:\n" "Trennen von Gruppen (Groesse:Strafe): %s\n" "Strafe kein Gruppenmitglied am Nachbartisch : %s\n" "Strafe fuer eine Person allein: %s" % ( self.GruppenTrennen, self.NichtNachbar, self.Allein) ) def gibPunkte(self, Typ, Gruppengroesse=0, N_Teilungen=0, freiePlaetze=0): if Typ == 'allein': return self.Allein elif Typ == 'nichtNachbar': return self.NichtNachbar elif Typ == 'Tischbesetzung': try: return self.TischWert[freiePlaetze] except KeyError: return 0 elif Typ == 'Trennung': if (N_Teilungen == 0): return 0 elif (N_Teilungen == 1) & (Gruppengroesse < 2): raise IndexError("Gruppe sind mindestens zwei Leute") elif (N_Teilungen == 2) & (Gruppengroesse < 3): raise IndexError("mindestens drei Leute noetig") elif (N_Teilungen > 2): return -50 if Gruppengroesse in self.GruppenTrennen[N_Teilungen]: return self.GruppenTrennen[N_Teilungen][Gruppengroesse] else: return self.gibPunkte('Trennung',Gruppengroesse-1, N_Teilungen) else: raise IndexError("Bezeichner gibts nicht") def laden( self, FileName ): print("-- Strafliste laden") config = configparser.ConfigParser() config.read( os.path.expanduser(FileName) ) # ast.literal_eval statt eval: die Werte sind reine Literale # (Zahlen und Dicts wie {2:-10}), also kein Code noetig. self.NichtNachbar = ast.literal_eval(config.get( 'Globals', 'NichtNachbar' )) self.Allein = ast.literal_eval(config.get( 'Globals', 'Allein' )) self.TischWert = ast.literal_eval( config.get( 'Globals', 'TischWertigkeiten' ) ) for (k,v) in config.items( 'Trennung' ): self.GruppenTrennen[int(k)] = ast.literal_eval(v) # Sitzplatzverteilung, bestehend aus den Einzelelementen @total_ordering class Sitzplatzverteilung: """Loesungsvorschlag fuer eine Sitzplatzordung""" # Aenderung der Sitzplatzverteilung (Aktion) def __init__(self, Tische=[], Gruppen=[], Strafen='', VIPListe={}, VIPs=[] ): """erzeugt eine Sitzplatzverteilung nach dem Zufallsprinzip Zufallgenerator, um zu entscheiden, nach welcher Verteilungsart verfahren wird: - zuerst die groessten Gruppen auf die passenden Tische verteilen, solange sie noch an einen Tisch passen oder - zuerst alle kleinsten Gruppen, da man die nicht mehr teilen sollte """ #print("+ Sitzplatzverteilung\n") if Gruppen.NLeute > Tische.NStuehle: print("Anzahl der Leute:", Gruppen.NLeute) print("Anzahl der Plaetze:", Tische.NStuehle) raise IndexError("zu wenig Stuehle") self.MutationsGroesse = 2 self.VIP = VIPs self.VIPListe = VIPListe self.Gruppen = Gruppen self.Strafen = Strafen self.Tische = Tische.deepcopy() ## self.Tische = Tische self.Groesster = Tische.Groesster self.value = 0 self.Tid_Value = {} self.Gid_Value = {} self.Gid_Tid = {} # zuerst die VIPs an die richtigen Tische setzen self.VIPsSetzen( VIPListe, VIPs, self.Tische ) # fuehre Buch ueber die ab jetzt freien Plaetze self.info = Plaetze( self.Tische ) # alle Gruppen auf die Tische verteilen self.GruppenSetzen( Gruppen ) # die Sitzplatzverteilung bepunkten self.bewerten() print("erzeugt: ", self.value) def __repr__(self): """Ausgabe der Sitzplatzverteilung an der Konsole""" r = "-- Sitzplatzverteilung:\n" Tids = [] for T in self.Tische: Tids.append(T.Id) Tids.sort() for Tid in Tids: T = self.Tische.vonId(Tid) r = r + repr(T) + "\n" r = r + " Gruppenwertigkeiten:\n" Gids = [] for G in self.Gruppen: Gids.append(G.Id) Gids.sort() for Gid in Gids: G = self.Gruppen.vonId(Gid) r = r + " Gruppe " + repr(G.Id) + " (" + repr(self.GruppenWertGet(G)) + ")" r = r + ", Teilungen: " + repr(self.GruppenTeilungGet(G)) + "\n" r = r + " Tischwertigkeiten:\n" for Tid in Tids: T = self.Tische.vonId(Tid) r = r + " Tisch " + repr(T.Id) + " (" + repr(self.TischWertGet(T)) + ")\n" r = r + "Wertigkeit:" + repr(self.value) + "\n" return r def speichern(self, OutputTP, OutputPT ): """Ausgabe der Sitzplatzverteilung in ein File""" Tids = [] for T in self.Tische: Tids.append(T.Id) Tids.sort() Liste = [] # rausschreiben Tisch -> Personen fout = open( OutputTP, 'w+') for Tid in Tids: T = self.Tische.vonId(Tid) fout.write( "["+ repr(T.Id)+ "]\n" ) PlatzNummer = 1 for P in T: str = "" str = str + P._Titel + " " str = str + P._Vorname + " " str = str + P._Name + " = " str = str + "Hof " + repr(T.Hof) + ", " str = str + "Tisch "+ repr( T.Nummer ) + ", " str = str + "Nummer "+ repr( PlatzNummer ) str = str + "\n" PlatzNummer = PlatzNummer + 1 fout.write( str ) str = "" str = str + P._Name + ", " str = str + P._Vorname + ", " str = str + P._Titel + " = " str = str + "Hof " + repr(T.Hof) + ", " str = str + "Tisch "+ repr( T.Nummer ) + ", " str = str + "Nummer "+ repr( PlatzNummer ) str = str + "\n" Liste.append( str ) fout.write( "\n" ) fout.close() # rausschreiben Personen -> Tisch Liste.sort() fout = open( OutputPT, 'w+') fout.writelines( Liste ) fout.close() # Farbpalette fuer die Gruppen-Einfaerbung im SVG (wird zyklisch # wiederverwendet, falls es mehr Gruppen als Farben gibt) SVGGruppenFarben = [ '#e6194b', '#3cb44b', '#4363d8', '#f58231', '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4', '#469990', '#dcbeff', '#9a6324', '#800000', '#aaffc3', '#000075' ] def alsSVG(self, OutputSVG, PersonenRadius=12, Puffer=10): """Zeichnet die Sitzplatzverteilung als SVG: Tische werden als weisse Kreise dargestellt, Personen als farbige Kreise (Farbe = Gruppe) innerhalb ihres Tischkreises. Das Koordinatenraster der Tische wird so skaliert, dass alle Personenkreise in ihrem Tischkreis Platz haben und sich die Tischkreise untereinander nicht ueberschneiden. """ # Tischradius je Tisch berechnen: gross genug, damit alle # Personenkreise (auf einem inneren Kreis gleichmaessig verteilt) # nebeneinander Platz haben, ohne sich zu beruehren. Der Faktor # 1.1 auf den rechnerischen Mindestabstand ist ein kleiner # Sicherheitsabstand gegen Rundungsfehler und damit sich die # Kreise auch optisch sichtbar nicht beruehren. def SitzRadiusVon(Plaetze): if Plaetze <= 1: return 0.0 return 1.1 * PersonenRadius / math.sin(math.pi / Plaetze) def TischRadius(Plaetze): return SitzRadiusVon(Plaetze) + PersonenRadius + Puffer TidZuRadius = {} GroessterRadius = 0 for T in self.Tische: r = TischRadius( T.Plaetze ) TidZuRadius[T.Id] = r if r > GroessterRadius: GroessterRadius = r # Rasterabstand der Tisch-Koordinaten: doppelter groesster # Tischradius (plus Puffer) verhindert Ueberschneidungen # unabhaengig davon, welche Tische tatsaechlich benachbart sind Rasterabstand = 2 * GroessterRadius + Puffer Xs = [ T.X_Koordinate for T in self.Tische ] Ys = [ T.Y_Koordinate for T in self.Tische ] MinX, MinY = min(Xs), min(Ys) MaxX, MaxY = max(Xs), max(Ys) Rand = GroessterRadius + Puffer Breite = (MaxX - MinX) * Rasterabstand + 2 * Rand Hoehe = (MaxY - MinY) * Rasterabstand + 2 * Rand def TischMitte(T): x = (T.X_Koordinate - MinX) * Rasterabstand + Rand y = (T.Y_Koordinate - MinY) * Rasterabstand + Rand return (x, y) def GruppeVonPerson(P): try: return self.Gruppen.GruppeVonPid(P.Id) except KeyError: return None GidZuFarbe = {} def FarbeVonGruppe(G): if G is None: return '#888888' if G.Id not in GidZuFarbe: Index = len(GidZuFarbe) % len(self.SVGGruppenFarben) GidZuFarbe[G.Id] = self.SVGGruppenFarben[Index] return GidZuFarbe[G.Id] Teile = [] Teile.append( '\n' ) Teile.append( '\n' % (Breite, Hoehe, Breite, Hoehe) ) Teile.append( '\n' ) for T in self.Tische: Mx, My = TischMitte(T) TR = TidZuRadius[T.Id] Teile.append( '\n' % (Mx, My, TR) ) Plaetze = list(T) n = len(Plaetze) SitzRadius = SitzRadiusVon( T.Plaetze ) for i, P in enumerate(Plaetze): Winkel = 2 * math.pi * i / max(n, 1) - math.pi / 2 Px = Mx + SitzRadius * math.cos(Winkel) Py = My + SitzRadius * math.sin(Winkel) Farbe = FarbeVonGruppe( GruppeVonPerson(P) ) Teile.append( '\n' % (Px, Py, PersonenRadius, Farbe) ) # Tischnummer zentriert in die Tischmitte (nach den Personen- # kreisen gezeichnet, damit sie oben liegt; ein halbtransparenter # weisser Kreis darunter haelt sie auch ueber Personenkreisen # lesbar, falls die Tischmitte belegt sein sollte) Teile.append( '\n' % (Mx, My, PersonenRadius) ) Teile.append( '%s\n' % (Mx, My, PersonenRadius * 1.1, T.Nummer) ) Teile.append( '\n' ) fout = open( OutputSVG, 'w' ) fout.writelines( Teile ) fout.close() def mutieren(self): """Aendert die vorhandene Sitzplatzverteilung hoffentlich zum Besseren""" [IdsSchlechteGruppen, Wertigkeiten] = self.SchlechteGruppenTopX(Anzahl=self.MutationsGroesse) SchlechteGruppen = [] for Gid in IdsSchlechteGruppen: G = self.Gruppen.vonId(Gid) self.GruppeEntfernen(G) SchlechteGruppen.append(G) self.GruppenSetzen( SchlechteGruppen ) self.bewerten() print("mutiert: ", self.value) return self def __eq__(self,other): return self.value == other.value def __lt__(self,other): return self.value < other.value def bewerten(self): """Errechnet die momentane Wertigkeit der Sitzplaetze""" self.GesamtWertSet( 0 ) # Tische bewerten for T in self.Tische: #print(T) n_freie = T.freiePlaetze() Strafpunkte = self.Strafen.gibPunkte( 'Tischbesetzung', freiePlaetze=n_freie ) self.TischWertSet( T, Strafpunkte ) self.GesamtWertAdd( Strafpunkte ) #self.GruppenWertAdd( Gruppe, self.Strafen.gibPunkte( 'nichtNachbar') ) # schauen wie oft eine Gruppe getrennt wurde for G in self.Gruppen: NTeilungen = self.GruppenTeilungGet(G) Strafpunkte = self.Strafen.gibPunkte( 'Trennung', G.Anzahl(), NTeilungen ) self.GruppenWertSet( G, Strafpunkte ) self.GesamtWertAdd( Strafpunkte ) # schauen ob Leute an einem Tisch allein sind Strafpunkte = self.Strafen.gibPunkte( 'allein' ) for T in self.Tische: Gids = self.LeuteAllein( T ) for Gid in Gids: G = self.Gruppen.vonId( Gid ) self.GruppenWertAdd( G, Strafpunkte ) self.GesamtWertAdd( Strafpunkte ) def Wertigkeit(self): """Ausgabe der Guete der Sitzplatzverteilung an der Konsole""" return ' L '+ repr(self.value) # Personenspezifische Funktionen def PersonSetzen(self, Person, Tisch): """setzt eine Person an den Tisch""" Tisch.Person_dazu( Person ) def PersonEntfernen(self, Person, Tisch): """entfernt eine Person von dem Tisch""" Tisch.Person_weg( Person ) FreiVorher = Tisch.freiePlaetze() # der Tisch hat jetzt weniger freie Plaetze self.info.update( Tisch, FreiVorher ) def PlatzTauschen(self, neuePerson, altePerson, Tisch): """Tauscht zwischen zwei Personen den Sitzplatz""" self.PersonEntfernen( altePerson, Tisch ) self.PersonSetzen( neuePerson, Tisch ) # Gruppenspezifische Funktionen def GruppenSetzen(self, Gruppen ): """setzt mal die grossen mal die kleinen Gruppen auf beliebige Plaetze""" # Mache Liste der Gruppen, der Groesse nach sortiert Gruppenliste = [] for G in Gruppen: Gruppenliste.append(G) # uns setzte sie auf freie Plaetze while len(Gruppenliste) > 0: wahl = choice(['grosse', 'kleine']) if wahl == 'grosse': G = Gruppenliste.pop(0) else: G = Gruppenliste.pop(-1) self.GruppeSetzen(G) def GruppeSetzen(self, Gruppe, Tisch=None ): """setzt eine Gruppe irgendwohin wo Platz ist oder an einen gewuenschten Tisch """ ## print "---------------------" ## print "GruppeSetzen: Gruppe:", Gruppe if Tisch is None: Tisch = self.info.mindestensNFreie(Gruppe.Anzahl()) # kein Tisch mehr in der gewuenschten groesse frei. # also Gruppe trennen if Tisch is None: # neue Gruppengroesse zufaellig bestimmen Teil1 = choice( range(Gruppe.Anzahl()) ) [G1, G2] = Gruppe.teilen( Teil1 ) # Benachbarte Tische suchen die gross genug sind n1 = G1.Anzahl() n2 = G2.Anzahl() [TA,TNA] = self.info.NachbarTische(n1, n2) [TB,TNB] = self.info.NachbarTische(n2, n1) if TA is None and TB is None: # keine Nachbarn gefunden, also irgendwo hinsetzen self.GruppeSetzen(G1) self.GruppeSetzen(G2) elif TA is None: self.GruppeSetzen(G2,TB) self.GruppeSetzen(G1,TNB) elif TB is None: self.GruppeSetzen(G1,TA) self.GruppeSetzen(G2,TNA) else: # nimm die Kombination, die die Tische am besten fuellt vollA = 0 vollB = 0 if TA.istvoll(n1): vollA = vollA + 1 if TNA.istvoll(n2): vollA = vollA + 1 if TB.istvoll(n2): vollB = vollB + 1 if TNB.istvoll(n1): vollB = vollB + 1 if vollA > vollB: self.GruppeSetzen(G1,TA) self.GruppeSetzen(G2,TNA) else: self.GruppeSetzen(G2,TB) self.GruppeSetzen(G1,TNB) else: FreiVorher = Tisch.freiePlaetze() for P in Gruppe: self.PersonSetzen(P, Tisch) # der Tisch hat jetzt weniger freie Plaetze self.info.update( Tisch, FreiVorher ) # zur Gruppe merken, an welchen Tischen sie jetzt sitzt try: Ids = self.Gid_Tid[Gruppe.Id] Ids.add(Tisch.Id) except KeyError: s = set() s.add(Tisch.Id) self.Gid_Tid[Gruppe.Id] = s def GruppeEntfernen(self, Gruppe): """entfernt eine Gruppe von Ihren Tischen""" TischIds = self.Gid_Tid[Gruppe.Id] for Tid in TischIds: T = self.Tische.vonId(Tid) FreiVorher = T.freiePlaetze() for P in Gruppe: #print "P:", P try: self.PersonEntfernen( P, T ) except IndexError: pass except KeyError: pass self.info.update( T, FreiVorher ) del self.Gid_Tid[Gruppe.Id] # der Tisch hat jetzt weniger freie Plaetze # print self.Tische # Bewertungsfunktionen (Evaluation) # entscheiden bei der Mutation der Sitzplatzverteilung welche Tische als # erstes wieder aufgeloest werden und welche nicht so schnell def GesamtWertSet(self, Wert): """setzt die Gesamtwertung auf einen bestimmten Wert""" self.value = Wert def GesamtWertGet(self): """gibt den Wert der Gesamtwertung zurueck""" return self.value def GesamtWertAdd(self, Wert): """fuegt den Wert zur Gesamtwertung hinzu""" self.value = self.value + Wert def GruppenWertSet(self, Gruppe, Wert): self.Gid_Value[Gruppe.Id] = Wert def GruppenWertGet(self, Gruppe): """gibt die Zufriedenheit einer Gruppe zurueck je nachdem, wie oft die Gruppe oft geteilt wurde (je nach deren Groesse, bei grossen laesst es sich nicht vermeiden) """ try: return self.Gid_Value[Gruppe.Id] except KeyError: return 0 def GruppenWertAdd(self, Gruppe, Wert): """fuegt den Wert der Gruppenwertigkeit hinzu """ try: self.Gid_Value[Gruppe.Id] = self.Gid_Value[Gruppe.Id] + Wert except KeyError: self.Gid_Value[Gruppe.Id] = Wert def TischWertSet(self, Tisch, Wert): """setzt die Wertigkeit dieses Tisches auf einen bestimmten Wert""" self.Tid_Value[Tisch.Id] = Wert def TischWertGet(self, Tisch): """Gib die Wertigkeit eines Tisches zurueck - wenn viele unzufriedene Leute an dem Tisch sitzen, ist die Wertigkeit schlecht - wenn nur noch ein Platz frei ist, ist die Wertigkeit des Tisches ebenfalls schlecht weil man niemanden mehr dort hinsetzen kann - wenn die Leute der eigenen Gruppe nicht an den Nachbartischen sitzen, ist die Wertigkeit auch nicht gut """ try: return self.Tid_Value[Tisch.Id] except KeyError: return 0 def LeuteAllein(self, Tisch): """Stellt fest wieviele Leute an diesem Tisch alleine sind, weil sie zu einer Gruppe gehoeren, die an diesem Tisch nicht vertreten ist """ Anzahl_von_Gid = {} Gids = [] for P in Tisch: try: G = self.Gruppen.GruppeVonPid(P.Id) except KeyError: # Person gehoert zu keiner Gruppe in self.Gruppen (z.B. VIP) continue try: Anzahl_von_Gid[G.Id] = Anzahl_von_Gid[G.Id] + 1 except KeyError: Anzahl_von_Gid[G.Id] = 1 for Gid, Anzahl in Anzahl_von_Gid.items(): if Anzahl == 1: Gids.append(Gid) return Gids # Funktionen fuer mehrere Einheiten (Information) def SchlechteTischeTopX(self, Anzahl=-1): """gibt eine List mit X Tischids zurueck, die schlechte Wertungen haben""" return self.TopX( self.Tid_Value, Anzahl ) def SchlechteGruppenTopX(self, Anzahl=-1): """gibt eine List mit X Gruppenids zurueck, die schlechte Wertungen haben""" return self.TopX( self.Gid_Value, Anzahl ) # Hilfsfunktionen def GruppenTeilungGet(self, Gruppe): try: return len( self.Gid_Tid[Gruppe.Id] ) - 1 except KeyError: return 0 def VIPsSetzen(self, VIPListe, VIPs, Tische ): """setze die VIPs auf Ihre Plaetze""" #print " VIPsSetzen:" for PersonId, TischId in VIPListe.items(): #print " VIPsSetzen: PersonId", PersonId #print " VIPsSetzen: TischId", TischId Person = VIPs.PersonVonPid(PersonId) Tisch = Tische.vonId(TischId) self.PersonSetzen( Person, Tisch ) def TopX(self, Liste, Anzahl=-1 ): """gibt eine Liste mit X Ids zurueck, die die schlechtesten Wertungen haben Achtung: Dies ist bewusst *nicht* heapq.nsmallest. Der laufende Schwellwert Min sinkt monoton mit, so dass nur Ids aufgenommen werden, die waehrend der Iteration einen neuen Tiefpunkt erreichen oder gleichziehen. Das Ergebnis haengt an der Iterationsreihenfolge und praegt das Mutationsverhalten des GA (mutieren() -> SchlechteGruppenTopX). Eine Umstellung auf heapq wuerde die Loesungsfindung veraendern und ist daher bewusst unterlassen. """ Min = 0 RListe=[] Wertigkeit=[] for Id, Wert in Liste.items(): if Wert <= Min: RListe.append(Id) Wertigkeit.append(Wert) Min = Wert return [RListe[-Anzahl:], Wertigkeit[-Anzahl:] ] # Sitzplatzverteilung, bestehend aus den Einzelelementen class JSONConfig: """liest die Gaesteliste aus einer bestellung.json ein (Nachfolger des frueheren XMLConfig; JSON hat die alte XML-Form abgeloest, siehe doc/dateiformate.md). Erwartete Struktur: { "gruppen": [ { "name": "Familie Huber", # optional, nur Anzeige "tisch": 1, # optional -> VIP-Fixplatz "anzahl": 1, # optional -> Sammelbuchung "personen": [ {"vorname": "Anna", "nachname": "Huber", "titel": ""} ] } ] } "tisch" macht die ganze Gruppe zu VIPs an diesem Tisch (laeuft nicht durch die GA-Platzierung). "anzahl" > 1 vervielfacht jede Person der Gruppe in so viele identische Buchungen (anonyme Sammelbuchung). "titel" ist optional. """ def HandleBestellung(self, Daten): """die geladene JSON-Struktur in Personen/Gruppen/VIPs zerlegen Rueckgabe: [ PN, GN, VIPGruppe, VipHash, GruppenNamen ] (wie CSVConfig; das fruehere XMLConfig gab die ersten vier Elemente zurueck - Aufrufer, die GruppenNamen nicht brauchen, koennen es ignorieren.) """ if not isinstance( Daten, dict ) or 'gruppen' not in Daten: raise ValueError("bestellung.json braucht ein Objekt mit dem " "Schluessel 'gruppen'") PN = Personen() GN = Gruppen() VIPGruppe = Gruppe( 0 ) VipHash = {} GruppenNamen = {} PID = 0 GID = 0 for Index, GruppeJSON in enumerate( Daten['gruppen'] ): if 'personen' not in GruppeJSON: raise ValueError("Gruppe %d: 'personen' fehlt" % (Index + 1)) IsVip = 'tisch' in GruppeJSON and GruppeJSON['tisch'] is not None if IsVip: try: TischNr = int( GruppeJSON['tisch'] ) except (TypeError, ValueError): raise ValueError("Gruppe %d: 'tisch' ist keine " "Tischnummer" % (Index + 1)) try: AnzahlReservierungen = int( GruppeJSON.get( 'anzahl', 1 ) ) except (TypeError, ValueError): raise ValueError("Gruppe %d: 'anzahl' ist keine Zahl" % (Index + 1)) if AnzahlReservierungen < 1: raise ValueError("Gruppe %d: 'anzahl' muss >= 1 sein" % (Index + 1)) # regulaere (nicht-VIP) Gruppen bekommen eine GID if not IsVip: GID = GID + 1 G = Gruppe( GID ) GruppenNamen[ GID ] = GruppeJSON.get( 'name', 'Gruppe %d' % GID ) for PersonJSON in GruppeJSON['personen']: Vorname = str( PersonJSON.get( 'vorname', '' ) ).strip() Nachname = str( PersonJSON.get( 'nachname', '' ) ).strip() Titel = str( PersonJSON.get( 'titel', '' ) ).strip() if Vorname == '' or Nachname == '': raise ValueError("Gruppe %d: jede Person braucht Vorname " "und Nachname" % (Index + 1)) for i in range( AnzahlReservierungen ): PID = PID + 1 P = Person( PID, Vorname, Nachname, Titel ) if IsVip: VipHash[ PID ] = TischNr VIPGruppe.Person_dazu( P ) else: PN.PersonAdd( P ) G.Person_dazu( P ) if not IsVip: if G.Anzahl() > 0: GN.Gruppe_dazu( G ) else: # leere Gruppe wieder verwerfen del GruppenNamen[ GID ] GID = GID - 1 return [ PN, GN, VIPGruppe, VipHash, GruppenNamen ] def laden(self, Bestellung): """lese alle Eingangsdaten aus einer JSON-Datei ein""" import json # utf-8-sig schluckt ein evtl. vorhandenes BOM fsock = open( Bestellung, 'r', encoding='utf-8-sig' ) Daten = json.load( fsock ) fsock.close() return self.HandleBestellung( Daten ) if __name__ == '__main__': print("\n-- Erzeuge Personen:") P1 = Person( 1, 'Michael', 'Stangl') P2 = Person( 2, 'Magnus', 'Mueller') P3 = Person( 3, 'Bernd Robert', 'Hoehn') P4 = Person( 4, 'Heinz', 'Ruehmann') P5 = Person( 5, 'Franz', 'Bruegge') P6 = Person( 6, 'Hubert', 'K') print(P1) print(P2) print(P3) print(P4) print(P5) print(P6) print("\n-- Mehrzahl von Personen:") PN = Personen([P1, P2, P3, P4, P5, P6]) print(PN) print("\n-- Erzeuge Gruppen mit den Personen:") G1 = Gruppe( 1, [P1, P2] ) G1.Person_dazu( P3 ) G2 = Gruppe( 2 ) G2.Person_dazu( P4 ) G3 = Gruppe( 3, [P5,P6] ) print("\n-- Stelle Gruppen dar:") print(G1) print(G2) print(G3) print("\n-- Durchlaufe Gruppe 3:") for P in G3: print(P) print("\n-- Mache Liste der Gruppen:") GN = Gruppen([G1,G2,G3]) print(GN) print("\n-- Durchlaufe Gruppen sortiert:") for G in GN: print(G) print("\n-- Teile Gruppe:") [GX, GY] = G1.teilen() print(GX) print(GY) [GX, GY] = G1.teilen(3) print(GX) print(GY) print("\n-- Erzeuge Tische mit Personen:") T1 = Tisch( 1, Plaetze=5, Koordinaten=(1,2), pids=[P1, P2], Nachbarliste=[2] ) print(T1) T2 = Tisch( 2, Plaetze=4, Koordinaten=(3,2), Nachbarliste=[1,3] ) T2.Person_dazu( P3 ) print(T2) T2.Person_dazu( P4 ) print(T2) T2.Person_weg( P4 ) print(T2) T3 = Tisch( 3, Plaetze=2, Koordinaten=(5,2), Nachbarliste=[2] ) print("\n-- Ist Tisch 2 voll oder leer?:") print("ist voll = ", T2.istvoll()) print("ist leer = ", T2.istleer()) print("\n-- Wieviele Plaetze sind an Tisch 1 noch frei ?:") print("Freie Plaetze = ", T1.freiePlaetze()) print("Besetzte Plaetze = ", T1.besetztePlaetze()) print("\n-- Stelle Tische dar:") print(T1) print(T2) print(T3) print("\n-- Ist Person Nummer 3 an Tisch 1 ?:") print(P1 in T1) print("-- Ist Person Nummer 4 an Tisch 1 ?:") print(P4 in T1) TE = Tische([T1,T2,T3]) print(TE) print("\n-- Hole Tisch 2 aufgrund seiner Id :") TX = TE.vonId( 2 ) print(TX) print("\n-- Hole Gruppe 2 aufgrund ihrer Id :") GX = GN.vonId( 2 ) print(GX) print("\n-- Hole Person 2 aufgrund ihrer Id aus den Gruppen:") PX = GN.PersonVonPid( 2 ) print(PX) print("\n-- schau wo noch mindestens 3 Plaetze frei sind:") info = Plaetze(TE) print(info.mindestensNFreie( 3 )) print("\n-- schau wo zwei Gruppen bestimmter Groesse nebeneinander sitzen koennen:") print(" zwei neben drei:") [TX,TY] = info.NachbarTische(2, 3) print(TX) print(TY) print(" drei neben zwei:") [TX,TY] = info.NachbarTische(3, 2) print(TX) print(TY) P1 = Person( 1, 'Michael', 'Stangl') P2 = Person( 2, 'Magnus', 'Mueller') P3 = Person( 3, 'Bernd Robert', 'Hoehn') P4 = Person( 4, 'Heinz', 'Ruehmann') P5 = Person( 5, 'Marlene', 'Dietrich') P6 = Person( 6, 'Sergio', 'Leone') P7 = Person( 7, 'Walter', 'Emmerlich') P8 = Person( 8, 'Harald', 'Junke') P9 = Person( 9, 'Willi', 'Brandt') P10 = Person( 10, 'Michael', 'Schumacher') P11 = Person( 11, 'Ralf', 'Schumacher') P12 = Person( 12, 'Bla', 'Button') P13 = Person( 13, 'Heinz Harald', 'Frenzen') # # T1 T2 T3 T4 # <3> <5> <3> <4> # # G1 G2 G3 G4 # <3> <2> <2> <6> # print("\n-- Mache Gruppen:") VIPGruppe = ( Gruppe( 1, [P1, P2, P3] ) ) G2 = ( Gruppe( 2, [P4, P5] ) ) G3 = ( Gruppe( 3, [P6, P7] ) ) G4 = ( Gruppe( 4, [P8, P9, P10, P11, P12, P13] ) ) GN = Gruppen([G2,G3,G4]) print(GN) print("\n-- Mache Tische:") T1 = ( Tisch( 1, Plaetze=3, Koordinaten=(1,1), Nachbarliste=[2] ) ) T2 = ( Tisch( 2, Plaetze=5, Koordinaten=(2,1), Nachbarliste=[1,3] ) ) T3 = ( Tisch( 3, Plaetze=3, Koordinaten=(3,1), Nachbarliste=[2,4] ) ) T4 = ( Tisch( 4, Plaetze=4, Koordinaten=(4,1), Nachbarliste=[3] ) ) TE = Tische([T1,T2,T3,T4]) print(TE) print("\n-- Mache Listen:") # Personenid - Tischid VIPListePT = { 3:1, 2:1, 1:1 } print("VIP Liste:", VIPListePT) # Strafliste # die Teilung einer grossen Gruppe ist nicht so schlimm # wie die einer kleinen Teilung = list() Teilung.append({'x':'x'}) Teilung.append({2:-10, 3:-8, 4:-6, 6:-4, 10:-2}) Teilung.append({3:-20, 4:-8, 6:-6, 10:-4}) Teilung.append({4:-30, 6:-10, 10:-6}) # Ein Platz an einem Tisch frei ist schlechter als zwei Plaetze frei TischWertigkeiten={ 1:-3, 2:-2, 3:-1 } SL = Strafliste(GruppenTrennen=Teilung, NichtNachbar=-3, Allein=-30, TischWert=TischWertigkeiten) print(SL) print("\n-- Teste Strafliste:") print("SL.gibPunkte('Trennung', 7,1):", SL.gibPunkte('Trennung', 7,1)) print("SL.gibPunkte('Trennung', 5,1):", SL.gibPunkte('Trennung', 5,1)) print("SL.gibPunkte('Trennung', 3,1):", SL.gibPunkte('Trennung', 3,1)) try: print("SL.gibPunkte('Trennung',(1,1):",SL.gibPunkte('Trennung',1,1)) except IndexError: print("Gruppe sind mindestens zwei") print("\n-- Baue Sitzplatzverteilung:") SV = Sitzplatzverteilung(Tische=TE, Gruppen=GN, Strafen=SL, VIPListe=VIPListePT, VIPs=VIPGruppe) print(SV) print("\n-- Mutiere Sitzplatzverteilung:") SV.mutieren() print(SV) print("\n-- Gibt die 3 Tische mit der schlechtesten Wertung:") [TischIds, Wertigkeit]=SV.SchlechteTischeTopX(3) print("TischIds:", TischIds) print("Wertigkeit:", Wertigkeit) print("\n-- Gibt die Gruppen mit der schlechtesten Wertung:") [GruppenIds, Wertigkeit]=SV.SchlechteGruppenTopX(3) print("GruppenIds:", GruppenIds) print("Wertigkeit:", Wertigkeit) else: pass