d4844ded67
- getGruppenId (Gruppen-Ebene, personen-unabhaengig: welche Gruppen mit welcher Anzahl je Tisch) und getPersonenId (Personen-Ebene: alphabetische Namen je Tisch) an Sitzplatzverteilung; Basis-Methoden an Loesung. Gleiche Gruppen-Verteilung -> gleiche getGruppenId und damit gleiche Wertigkeit, egal welche Personen die geteilten Plaetze einnehmen. - Farm verwirft nach jeder Erzeugung/Mutation Loesungen mit schon gesehener getGruppenId, so dass dieselbe Sitzordnung nicht mehrfach gehalten und bewertet wird (defensiv: Klassen ohne getGruppenId bleiben unberuehrt, Tests unveraendert). - Web: Varianten-Vergleich ueber getGruppenId statt der person-basierten _Signatur; gruppenId und personenId zusaetzlich im Ergebnis-JSON. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1494 lines
50 KiB
Python
Executable File
1494 lines
50 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 hashlib
|
|
import html
|
|
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)
|
|
def __hash__(self):
|
|
# stabil ueber die (eindeutige, fortlaufende) Id hashen statt ueber
|
|
# die Objekt-Identitaet: sonst haengt die Iterations-/pop()-Reihenfolge
|
|
# der set() aus Person-Objekten (Gruppe._Personen) an der Speicher-
|
|
# adresse und variiert pro Prozesslauf - das machte selbst mit --seed
|
|
# den GA-Lauf nichtreproduzierbar. Integer-Ids sind zudem nicht von
|
|
# PYTHONHASHSEED betroffen, daher prozessstabil.
|
|
return hash( self.Id )
|
|
def __eq__(self, other):
|
|
return isinstance( other, Person ) and self.Id == other.Id
|
|
|
|
@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, Sektion='default' ):
|
|
"""laedt ein Strafpunkte-Profil aus einer Sektion der strafen.cfg.
|
|
|
|
Jede Sektion ist ein vollstaendiges Bewertungs-'Vorgehen' mit allen
|
|
Angaben in einem gemeinsamen Namensraum. Der Aufrufer waehlt das
|
|
Profil ueber 'Sektion' (Vorgabe 'default'); so lassen sich mehrere
|
|
Bewertungsstrategien in derselben Datei pflegen. Die Trennungs-
|
|
Straffen stehen unter den Schluesseln 'Anzahl_Trennungen_N'."""
|
|
print("-- Strafliste laden (Profil '%s')" % Sektion)
|
|
config = configparser.ConfigParser()
|
|
config.read( os.path.expanduser(FileName) )
|
|
if not config.has_section( Sektion ):
|
|
raise ValueError(
|
|
"Strafprofil '%s' nicht in %s gefunden (vorhanden: %s)"
|
|
% ( Sektion, FileName, ', '.join(config.sections()) or '-' ) )
|
|
# 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( Sektion, 'NichtNachbar' ))
|
|
self.Allein = ast.literal_eval(config.get( Sektion, 'Allein' ))
|
|
# TischWertigkeiten ist optional; fehlt der Schluessel, werden freie
|
|
# Plaetze gar nicht bestraft (leeres Dict -> gibPunkte('Tischbesetzung')
|
|
# gibt 0), wodurch mehr Teilbelegungen moeglich sind.
|
|
if config.has_option( Sektion, 'TischWertigkeiten' ):
|
|
self.TischWert = ast.literal_eval( config.get( Sektion, 'TischWertigkeiten' ) )
|
|
else:
|
|
self.TischWert = {}
|
|
# AbstandFaktor ist optional; fehlt er, wird der Abstand getrennter
|
|
# Gruppen nicht bestraft (Faktor 0).
|
|
if config.has_option( Sektion, 'AbstandFaktor' ):
|
|
self.AbstandFaktor = ast.literal_eval(config.get( Sektion, 'AbstandFaktor' ))
|
|
# Trennungs-Straffen: Schluessel 'Anzahl_Trennungen_N' -> N Trennungen.
|
|
# frisches Dict, damit nicht der (mutable) Konstruktor-Default geteilt
|
|
# wird und Reste eines frueheren laden() bleiben.
|
|
self.GruppenTrennen = {}
|
|
for (k, v) in config.items( Sektion ):
|
|
# configparser liefert Schluessel klein geschrieben
|
|
if k.startswith( 'anzahl_trennungen_' ):
|
|
N = int( k.rsplit( '_', 1 )[-1] )
|
|
self.GruppenTrennen[N] = 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,
|
|
GruppenNamen=None):
|
|
"""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.
|
|
|
|
Jeder Personenkreis bekommt ein <title>-Kind (nativer Browser-
|
|
Tooltip beim Hover: "Vorname Nachname — Gruppe") sowie die Attribute
|
|
class="person" und data-gruppe/data-gid, damit eine einbettende
|
|
Seite darauf ein Hover-/Kasten-Verhalten aufsetzen kann. Optional
|
|
liefert GruppenNamen ({Gid: Anzeigename}) die lesbaren Gruppennamen;
|
|
fehlt es, wird "Gruppe <Id>" verwendet.
|
|
"""
|
|
GruppenNamen = GruppenNamen or {}
|
|
|
|
def GruppenName(G):
|
|
if G is None:
|
|
return 'VIP / feste Platzierung'
|
|
return GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id )
|
|
|
|
def PersonName(P):
|
|
Teile = [ t for t in (P._Titel, P._Vorname, P._Name) if t ]
|
|
return ' '.join( Teile )
|
|
# 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)
|
|
G = GruppeVonPerson(P)
|
|
Farbe = FarbeVonGruppe( G )
|
|
GName = GruppenName( G )
|
|
PName = PersonName( P )
|
|
# data-gid: -1 fuer VIPs/ohne Gruppe (keine self.Gruppen-Id)
|
|
Gid = G.Id if G is not None else -1
|
|
Teile.append(
|
|
'<circle class="person" data-gid="%s" data-gruppe="%s" '
|
|
'data-name="%s" cx="%g" cy="%g" r="%g" fill="%s" '
|
|
'stroke="black" stroke-width="1"><title>%s — %s</title>'
|
|
'</circle>\n'
|
|
% ( Gid, html.escape(GName, quote=True),
|
|
html.escape(PName, quote=True),
|
|
Px, Py, PersonenRadius, Farbe,
|
|
html.escape(PName), html.escape(GName) ) )
|
|
|
|
# Tischnummer und Platzzahl zentriert in die Tischmitte (nach den
|
|
# Personenkreisen gezeichnet, damit sie oben liegen; eine halb-
|
|
# transparente weisse Ellipse darunter haelt beide Zeilen auch
|
|
# ueber Personenkreisen lesbar, falls die Tischmitte belegt ist).
|
|
# Obere Zeile: Tischnummer (gross/fett), darunter die Platzzahl.
|
|
Teile.append(
|
|
'<ellipse cx="%g" cy="%g" rx="%g" ry="%g" fill="white" '
|
|
'fill-opacity="0.75"/>\n'
|
|
% (Mx, My, PersonenRadius, PersonenRadius * 1.3) )
|
|
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 * 0.4, PersonenRadius * 1.1, T.Nummer) )
|
|
Teile.append(
|
|
'<text x="%g" y="%g" text-anchor="middle" '
|
|
'dominant-baseline="central" font-size="%g" '
|
|
'fill="#333">%d Pl.</text>\n'
|
|
% (Mx, My + PersonenRadius * 0.7, PersonenRadius * 0.62, T.Plaetze) )
|
|
|
|
Teile.append( '</svg>\n' )
|
|
|
|
# als UTF-8 schreiben (die SVG deklariert encoding="UTF-8"): Namen
|
|
# enthalten Umlaute und der Gedankenstrich im <title> ist non-ASCII;
|
|
# ohne dies wuerde die Windows-Standardkodierung (cp1252) die Datei
|
|
# ungueltig machen.
|
|
fout = open( OutputSVG, 'w', encoding='utf-8' )
|
|
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():
|
|
# 'Allein' meint: eine Person ist von ihrer (mehrkoepfigen) Gruppe
|
|
# getrennt und sitzt ohne Gruppenmitglied am Tisch. Eine Gruppe der
|
|
# Groesse 1 (alleinreisender Gast) hat kein Gruppenmitglied, das
|
|
# fehlen koennte - sie ist nie "allein" in diesem Sinne und wird
|
|
# daher nicht bestraft (sie darf mit anderen am Tisch sitzen).
|
|
if Anzahl == 1 and self.Gruppen.vonId(Gid).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 getGruppenId(self):
|
|
"""eindeutige Id der Loesung auf GRUPPEN-Ebene (personen-unabhaengig).
|
|
|
|
Signatur: je Tisch, welche Gruppen mit wie vielen Mitgliedern dort
|
|
sitzen. Zwei Loesungen mit identischer Gruppen-Verteilung bekommen
|
|
dieselbe Id - egal welche konkreten Personen die geteilten Plaetze
|
|
einnehmen (die Teilung einer 5er-Gruppe auf die Tische 3 und 4 ist
|
|
dieselbe Sitzordnung, gleich welche Personen wohin kommen). Da die
|
|
Bewertung (Strafliste) nur von den Gruppen-Anzahlen je Tisch, den
|
|
Nachbarschaften und der Auslastung abhaengt, haben Loesungen mit
|
|
gleicher getGruppenId auch dieselbe Wertigkeit. Diese Id dient dem
|
|
Vergleich der Loesungen (Dedup), damit dieselbe Sitzordnung nicht
|
|
mehrfach als eigene Loesung gehalten und bewertet wird."""
|
|
Teile = []
|
|
for T in sorted( self.Tische, key=lambda t: t.Id ):
|
|
Zaehler = {}
|
|
for P in T:
|
|
try:
|
|
Gid = self.Gruppen.GruppeVonPid( P.Id ).Id
|
|
except KeyError:
|
|
Gid = -1 # VIP / feste Platzierung (ohne GA-Gruppe)
|
|
Zaehler[Gid] = Zaehler.get( Gid, 0 ) + 1
|
|
Teile.append( (T.Id, tuple(sorted( Zaehler.items() ))) )
|
|
return hashlib.sha1( repr(Teile).encode('utf-8') ).hexdigest()[:16]
|
|
def getPersonenId(self):
|
|
"""eindeutige Id der Loesung auf PERSONEN-Ebene.
|
|
|
|
Signatur: je Tisch die alphabetisch sortierten Personennamen.
|
|
Identifiziert WIRKLICH identische Sitzbelegungen (dieselben Personen
|
|
an denselben Tischen) - feiner als getGruppenId, das nur die Gruppen-
|
|
Verteilung vergleicht."""
|
|
Teile = []
|
|
for T in sorted( self.Tische, key=lambda t: t.Id ):
|
|
Namen = sorted(
|
|
' '.join( s for s in (P._Titel, P._Vorname, P._Name) if s )
|
|
for P in T )
|
|
Teile.append( (T.Id, tuple(Namen)) )
|
|
return hashlib.sha1( repr(Teile).encode('utf-8') ).hexdigest()[:16]
|
|
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
|