Files
platz/libs/Strukturdaten.py
T
Michael Stangl 6710d4f06a Bessere Verteilung: Strafen/Mutation, plus Abstammungsbaum mit Id-Herkunft
Weniger vermeidbare Gruppentrennungen:
- strafen.cfg: Tischbesetzung bestraft jetzt gestaffelt nach freien
  Plaetzen (teilbelegte Tische am teuersten, mit Fallback auf den
  naechstkleineren Eintrag), Trennungsstrafen deutlich erhoeht - so
  werden Gruppen nicht mehr getrennt, obwohl leere Tische danebenstehen
- gibPunkte('Tischbesetzung') mit Naechst-kleiner-Fallback
- mutieren() setzt geraeumte Gruppen groesste-zuerst neu
  (GruppenSetzen(GrosseZuerst=True)), damit grosse Gruppen die frei
  gewordenen ganzen Tische bekommen und wieder zusammenkommen
  (uebrig bleiben nur physisch unvermeidbare Trennungen, Gruppe > Tisch)

Abstammungsbaum (neuer Schalter --show-lineage -> Abstammung.svg):
- neue Klasse ga.Abstammung zeichnet den vollen Mutationsbaum aller
  Loesungen; der Ast der finalen besten Loesung ist hervorgehoben und
  mit ihrer Id beschriftet
- hierarchische Loesungs-Ids (Farm._WurzelId/_KindId): Wurzel '7',
  Mutationskind '7.2', dessen Kind '7.2.1' - die ganze Abstammung ist
  direkt aus der Id ablesbar

Tests (15 gruen), README und CLAUDE.md aktualisiert; Beispiele mit den
neuen Strafen und beiden Grafiken neu berechnet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:38:43 +02:00

1376 lines
44 KiB
Python
Executable File

"""
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={}, AbstandFaktor=0.0):
self.GruppenTrennen = GruppenTrennen
self.NichtNachbar = NichtNachbar
self.Allein = Allein
self.TischWert = TischWert
# Strafpunkte je normierter Abstandseinheit (0..100) zwischen
# getrennten Teilen einer Gruppe; 0 = Abstand wird nicht bestraft.
self.AbstandFaktor = AbstandFaktor
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\n"
"Straffaktor je Abstandseinheit getrennter Gruppen: %s"
% ( self.GruppenTrennen,
self.NichtNachbar,
self.Allein,
self.AbstandFaktor) )
def gibPunkte(self, Typ, Gruppengroesse=0, N_Teilungen=0, freiePlaetze=0, Abstand=0.0):
if Typ == 'allein':
return self.Allein
elif Typ == 'nichtNachbar':
return self.NichtNachbar
elif Typ == 'Abstand':
# Abstand ist bereits auf 0..100 normiert (siehe bewerten());
# je groesser der Abstand, desto mehr Strafe.
return self.AbstandFaktor * Abstand
elif Typ == 'Tischbesetzung':
# Strafe fuer freie Plaetze an einem (teil-)belegten Tisch. Gibt
# es keinen exakten Eintrag fuer 'freiePlaetze', wird auf den
# naechstkleineren konfigurierten Wert zurueckgegriffen, damit man
# nicht jede moegliche Platzzahl auflisten muss (z.B. deckt ein
# Eintrag fuer 4 auch 5..7 ab, bis der naechste Eintrag greift).
# Auch ein komplett leerer Tisch (freiePlaetze == Plaetze) wird
# bewertet - er trifft in der Regel den hoechsten konfigurierten
# Eintrag (z.B. 8 frei bei 8er-Tisch) oder dessen Fallback.
if freiePlaetze in self.TischWert:
return self.TischWert[freiePlaetze]
kleinere = [ k for k in self.TischWert if k < freiePlaetze ]
if kleinere:
return self.TischWert[ max(kleinere) ]
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' ) )
# Abschnitt [Abstand] ist optional; fehlt er, wird der Abstand
# getrennter Gruppen nicht bestraft (Faktor 0).
if config.has_section( 'Abstand' ):
self.AbstandFaktor = ast.literal_eval(config.get( 'Abstand', 'Faktor' ))
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, Verbindungen=True):
"""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.
Ist Verbindungen=True (Vorgabe), wird jede ueber mehrere Tische
getrennte Gruppe zusaetzlich mit einer Linie in ihrer Gruppenfarbe
verbunden, so dass die Zusammengehoerigkeit sichtbar bleibt.
"""
# 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]
# Fuer jede Gruppe pro Tisch den Mittelpunkt (Zentroid) ihrer
# an diesem Tisch sitzenden Personenkreise sammeln. Die Reihenfolge
# der Iteration (Tische, darin Personen) stimmt mit der spaeteren
# Zeichenschleife ueberein, damit die per FarbeVonGruppe() lazy
# vergebenen Farben konsistent sind.
GidZuTischZentroiden = {}
for T in self.Tische:
Mx, My = TischMitte(T)
Plaetze = list(T)
n = len(Plaetze)
SitzRadius = SitzRadiusVon( T.Plaetze )
GidZuSummen = {}
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)
G = GruppeVonPerson(P)
if G is None:
continue
FarbeVonGruppe(G) # Farbe frueh (aber in Zeichenreihenfolge) festlegen
sx, sy, cnt = GidZuSummen.get( G.Id, (0.0, 0.0, 0) )
GidZuSummen[G.Id] = (sx + Px, sy + Py, cnt + 1)
for Gid, (sx, sy, cnt) in GidZuSummen.items():
GidZuTischZentroiden.setdefault( Gid, [] ).append(
(sx / cnt, sy / cnt) )
Teile = []
Teile.append( '<?xml version="1.0" encoding="UTF-8"?>\n' )
Teile.append(
'<svg xmlns="http://www.w3.org/2000/svg" width="%g" height="%g" '
'viewBox="0 0 %g %g">\n' % (Breite, Hoehe, Breite, Hoehe) )
Teile.append( '<rect width="100%" height="100%" fill="white"/>\n' )
# Getrennte Gruppen (an mehr als einem Tisch vertreten) mit einer
# Linie in ihrer Gruppenfarbe verbinden, damit die Zusammengehoerigkeit
# sichtbar bleibt. Die Linie laeuft durch die Tisch-Zentroiden der
# Gruppe (Mittelpunkte ihrer Personenkreise je Tisch); bei mehr als
# zwei Tischen als durchgehender Streckenzug entlang der laengsten
# Achse. Die Linienbreite entspricht dem halben Sitzdurchmesser
# (= Sitzradius = PersonenRadius). Vor den Tischen gezeichnet, damit
# sie darunter liegt. Nur wenn Verbindungen aktiv sind.
LinienBreite = PersonenRadius
for Gid, Zentroiden in ( GidZuTischZentroiden.items() if Verbindungen
else [] ):
if len(Zentroiden) < 2:
continue
# Zentroiden entlang der am weitesten auseinanderliegenden Achse
# ordnen, damit der Streckenzug sie ohne Ueberkreuzungen durchlaeuft
F1, F2 = Zentroiden[0], Zentroiden[1]
MaxDist2 = -1.0
for i in range(len(Zentroiden)):
for j in range(i + 1, len(Zentroiden)):
ax, ay = Zentroiden[i]
bx, by = Zentroiden[j]
d2 = (ax - bx) ** 2 + (ay - by) ** 2
if d2 > MaxDist2:
MaxDist2 = d2
F1, F2 = Zentroiden[i], Zentroiden[j]
Dx, Dy = F2[0] - F1[0], F2[1] - F1[1]
Geordnet = sorted( Zentroiden,
key=lambda P: (P[0] - F1[0]) * Dx + (P[1] - F1[1]) * Dy )
Punkte = ' '.join( '%g,%g' % (px, py) for (px, py) in Geordnet )
Farbe = FarbeVonGruppe( self.Gruppen.vonId(Gid) )
Teile.append(
'<polyline points="%s" fill="none" stroke="%s" '
'stroke-width="%g" stroke-linecap="round" '
'stroke-linejoin="round" stroke-opacity="0.5"/>\n'
% (Punkte, Farbe, LinienBreite) )
for T in self.Tische:
Mx, My = TischMitte(T)
TR = TidZuRadius[T.Id]
Teile.append(
'<circle cx="%g" cy="%g" r="%g" fill="white" '
'stroke="black" stroke-width="1.5"/>\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(
'<circle cx="%g" cy="%g" r="%g" fill="%s" '
'stroke="black" stroke-width="1"/>\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(
'<circle cx="%g" cy="%g" r="%g" fill="white" '
'fill-opacity="0.75"/>\n' % (Mx, My, PersonenRadius) )
Teile.append(
'<text x="%g" y="%g" text-anchor="middle" '
'dominant-baseline="central" font-size="%g" '
'font-weight="bold" fill="black">%s</text>\n'
% (Mx, My, PersonenRadius * 1.1, T.Nummer) )
Teile.append( '</svg>\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)
# beim Reseat die groessten zuerst, damit sie die durch das Entfernen
# frei gewordenen (ganzen) Tische bekommen und wieder zusammenkommen,
# statt erneut getrennt zu werden
self.GruppenSetzen( SchlechteGruppen, GrosseZuerst=True )
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') )
# Bezugsgroesse fuer den Abstand getrennter Gruppen: maximale
# Ausdehnung der Tischanordnung (groessere von Breite/Laenge im
# Koordinatenraster). Ein Abstand von dieser Groesse entspricht dem
# normierten Wert 100 (Gruppenteile an gegenueberliegenden Enden).
Xs = [ T.X_Koordinate for T in self.Tische ]
Ys = [ T.Y_Koordinate for T in self.Tische ]
MaxAusdehnung = max( max(Xs) - min(Xs), max(Ys) - min(Ys) ) if Xs else 0
# 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 )
# Bei getrennten Gruppen zusaetzlich den raeumlichen Abstand
# zwischen den belegten Tischen bestrafen: groesser = schlechter.
if NTeilungen >= 1 and MaxAusdehnung > 0:
Tische = [ self.Tische.vonId(Tid) for Tid in self.Gid_Tid[G.Id] ]
MaxDist = 0.0
for i in range(len(Tische)):
for j in range(i + 1, len(Tische)):
dx = Tische[i].X_Koordinate - Tische[j].X_Koordinate
dy = Tische[i].Y_Koordinate - Tische[j].Y_Koordinate
d = math.sqrt( dx * dx + dy * dy )
if d > MaxDist:
MaxDist = d
Abstand = MaxDist / MaxAusdehnung * 100
AbstandStrafe = self.Strafen.gibPunkte( 'Abstand', Abstand=Abstand )
self.GruppenWertAdd( G, AbstandStrafe )
self.GesamtWertAdd( AbstandStrafe )
# 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, GrosseZuerst=False ):
"""setzt Gruppen auf freie Plaetze.
GrosseZuerst=False (Erstbelegung): mal die grossen, mal die kleinen
zuerst (Zufall) - erzeugt Vielfalt in der Startpopulation.
GrosseZuerst=True (Mutation): konsequent die groesste Gruppe zuerst,
damit sie einen noch komplett freien Tisch bekommt und nicht getrennt
werden muss, weil kleine Gruppen die freien Tische schon anbrechen.
"""
# 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:
if GrosseZuerst:
# groesste zuerst: sie beanspruchen die noch komplett freien
# Tische, bevor kleine Gruppen diese teilbelegen (sonst muessen
# grosse Gruppen mangels ganzem Tisch getrennt werden)
G = max( Gruppenliste, key=lambda g: g.Anzahl() )
Gruppenliste.remove(G)
else:
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