Compare commits

...

3 Commits

Author SHA1 Message Date
Michael Stangl 9a52fe5550 Add unittest suite for Sitzplatzverteilung with three scenarios
Covers three concrete seating layouts to exercise the GA's group
placement and splitting logic:
- three tables chained pairwise (2 seats each), three couples ->
  no group gets split, optimal score of 0.
- four tables in a line (2 seats each), two groups of four -> each
  group ends up split across one contiguous table pair (T1+T2 or
  T3+T4), never mixed across the middle seam.
- six tables in a circle (2 seats each), four groups of three ->
  each group is split exactly once (2+1), since no table has three
  seats; with the fixed seed the split pairs are also true circle
  neighbors.

Also documents how to run the suite in README.md, including the
PYTHONPATH workaround needed when a stray global env var shadows
the stdlib "tests" package name with an unrelated project's tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 12:42:45 +02:00
Michael Stangl 405319b075 Document Abfolge/Anzahl action letters in zyklus.cfg header
Adds a comment header explaining what e/s/S/m/j/z mean in the
Abfolge sequence, referencing the corresponding Farm actions in
libs/ga.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 12:28:42 +02:00
Michael Stangl 6e3649ed27 Port codebase from Python 2 to Python 3
Replace Python 2-only constructs throughout libs/: print statements,
ConfigParser -> configparser, sets.Set -> builtin set, string.split ->
str.split, xrange -> range, dict.has_key()/.iteritems() -> in/.items(),
old-style raise Exception, "msg" syntax, and __cmp__ -> __eq__/__lt__
via functools.total_ordering (Python 3 dropped cmp()/__cmp__, which
max()/.sort() rely on). Also cleaned up mixed tab/space indentation
that Python 3's stricter tokenizer rejects.

Fixed two latent bugs that only surfaced once end-to-end runs were
possible under Python 3's stricter error handling:
- HandleBestellung() compared G.Anzahl (a bound method) to an int
  instead of calling G.Anzahl() - silently "worked" under Python 2's
  permissive cross-type ordering, raises TypeError under Python 3.
- LeuteAllein() raised KeyError for VIP-group members, who are never
  added to the regular Gruppen container; now skips people with no
  group entry instead of crashing.

Verified end-to-end: bin/platz.sh --indir work/test1 and --indir
work/test2 (VIP group included) both run to completion and produce
correctly formatted output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 12:27:27 +02:00
8 changed files with 488 additions and 284 deletions
+24 -8
View File
@@ -10,11 +10,27 @@ sizes/neighbor relationships, and a penalty scheme. It reads an XML order/guest
table layout, runs a small custom GA framework to search for a good seating, and writes the
result to text/CSV output files.
**This is Python 2 code** (print statements, `ConfigParser`, `sets.Set`, `UserList`,
`string.split`, old-style `raise Exception, "msg"`, `xrange`, `dict.has_key`,
`dict.iteritems`). It will not run under Python 3 without a 2to3-style port. There is no
build system, test suite, or linter configured in this repo. `requirements.txt` exists but
is empty/commented — everything used is standard library — it's there only so
**This is Python 3 code**, migrated from an original Python 2 implementation (the git
history / old comments may still say "Python 2" — that's now stale). Notable migration
artifacts to be aware of when touching this code:
- `Gruppe`, `Loesung`, and `Sitzplatzverteilung` use `@functools.total_ordering` with
`__eq__`/`__lt__` instead of the old Python 2 `__cmp__` (Python 3 dropped `__cmp__` and
`cmp()` entirely; `max()`/`.sort()` need rich comparisons).
- `sets.Set` → builtin `set`; `ConfigParser``configparser`; `string.split`
`str.split`; `xrange``range`; `dict.has_key(k)``k in dict`; `dict.iteritems()`
`dict.items()`; old-style `raise Exception, "msg"``raise Exception("msg")`.
- `Strukturdaten.LeuteAllein()` was fixed to skip people with no entry in
`self.Gruppen.GvonPid` (e.g. VIP-group members who were never added to the regular
`Gruppen` container) instead of letting `GruppeVonPid` raise `KeyError` — this was a
latent bug that only surfaced once VIP data (`work/test2`) was actually exercised
end-to-end.
- `HandleBestellung()` had a `G.Anzahl` comparison (`G.Anzahl > 0`) that used to silently
compare a bound method to an int under Python 2's permissive cross-type ordering;
Python 3 raises `TypeError` for that, which is what surfaced it. Fixed to call
`G.Anzahl()`.
There is no build system, test suite, or linter configured in this repo. `requirements.txt`
exists but is empty/commented — everything used is standard library — it's there only so
`bin/install_py.*` has something to install into the `.venv`.
## Running the program
@@ -39,8 +55,8 @@ Entry point is `libs/platz.py`, invoked via the scripts in `bin/` (each provided
"$PLATZ_LIBS/platz.py"`. This is the actual program entry point (replaces the old
`bin/run` / `bin/run.bat`, which hardcoded absolute paths and have been removed).
`libs/platz.py` requires a command-line switch `--indir <dir>` (parsed with `optparse`,
since this is Python 2) pointing at a directory that holds the per-run input files and
`libs/platz.py` requires a command-line switch `--indir <dir>` (parsed with `optparse`)
pointing at a directory that holds the per-run input files and
receives the output files — e.g. `work/test1/`, `work/test2/`, or any new folder following
the same layout:
- `tische.ini` — table definitions: id, `Nummer`, `Hof` (venue/court), `Plaetze` (seats),
@@ -79,7 +95,7 @@ Domain-agnostic and reusable in principle:
- `Farm` — owns the population (`Pool`/`PoolNeu`), and executes a `Zyklus` action-by-action
against instances of a given solution class (passed as `Klasse` plus its constructor
args/kwargs). `Bester()` returns the fittest (`max()`) solution found; solutions are ordered
via `__cmp__` on `self.value`.
via `__eq__`/`__lt__` (`@total_ordering`) on `self.value`.
### `Strukturdaten.py` — domain model for this specific seating problem
- `Person`, `Gruppe` (group — a `Set` of people, splittable via `teilen()`), `Tisch` (table —
+14 -4
View File
@@ -5,10 +5,9 @@ given group memberships, table sizes/neighbor relationships, and a configurable
scheme. It reads a guest list (XML) and a table layout (INI), searches for a good seating
using a small custom GA framework, and writes the result to text/CSV output files.
**This is Python 2 code** (print statements, `ConfigParser`, `sets.Set`, `UserList`,
`string.split`, old-style `raise Exception, "msg"`, `xrange`, `dict.has_key`,
`dict.iteritems`). There are no third-party dependencies (standard library only), so
`requirements.txt` is empty — it exists only so `bin/install_py` has something to install.
**This is Python 3 code** (migrated from an original Python 2 implementation). There are no
third-party dependencies (standard library only), so `requirements.txt` is empty — it
exists only so `bin/install_py` has something to install.
## Project structure
@@ -17,6 +16,7 @@ platz/
├── bin/ # environment + launcher scripts (.bat + .sh pairs)
├── cfg/ # GA cycle definition and penalty scheme
├── libs/ # the two Python modules (ga.py, Strukturdaten.py, platz.py)
├── tests/ # unittest suite for Strukturdaten.py
└── work/ # per-run input (table layout, guest list) and output
├── test1/
└── test2/
@@ -27,6 +27,16 @@ platz/
- `libs/Strukturdaten.py` — domain model for the seating problem (`Person`, `Gruppe`,
`Tisch`, `Strafliste`, `Sitzplatzverteilung`, `XMLConfig`).
## Running the tests
```bash
python -m unittest discover -s tests -p "test_*.py" -v
```
If a stray global `PYTHONPATH` entry shadows the standard-library `tests` package name
(unrelated to this project), clear it for the command, e.g. `PYTHONPATH= python -m
unittest discover -s tests -p "test_*.py" -v`.
## Setup
1. Install a Python interpreter (`py` on Windows / `python3` on Linux/macOS must be on
+14
View File
@@ -1,3 +1,17 @@
# Abfolge: Kommagetrennte Liste von Aktionsbuchstaben, die den Ablauf
# eines GA-Zyklus definieren. Jede Aktion greift auf den
# aktuellen Loesungspool zu (siehe Farm in libs/ga.py):
# e = erzeugen (neue zufaellige Loesungen erzeugen)
# s = selektieren (die besten Loesungen auswaehlen)
# S = zufaellig auswaehlen (zufaellige Loesungen auswaehlen)
# m = mutieren (bestehende Loesungen mutieren)
# j = behalten (alten Loesungspool als Eltern behalten)
# z = neue Generation (Pool durch die neu erzeugten Loesungen ersetzen)
#
# Anzahl: Kommagetrennte Liste von Zahlen, die zu jeder Aktion in der
# Abfolge gehoert und angibt, wie viele Loesungen davon
# betroffen sind ('x' = Aktion braucht keine Anzahl, z.B. j/z).
#
[Easy]
Abfolge='e,'+'s,z,m,j,z,'*3+'s'
Anzahl='10,'+'5,x,2,x,x,'*3+'1'
+147 -150
View File
@@ -9,12 +9,11 @@ __date__ = "$Date: $"
__copyright__ = "Copyright (c) 2005 Michael Stangl"
__license__ = "Python"
from sets import Set
from itertools import chain
from UserList import UserList
from random import *
import ConfigParser, os
import configparser, os
import copy
from functools import total_ordering
from xml.dom import minidom
@@ -29,14 +28,14 @@ class Person:
def __repr__(self):
return ' Person %s: "%s %s %s"' % ( self.Id, self._Titel,
self._Vorname, self._Name)
print self.__repr__
@total_ordering
class Gruppe:
"""enthalt alle Daten zu einer Gruppe"""
Art = 'Gruppe'
def __init__(self, Id, Personen=[]):
self.Id = Id
self._Personen = Set()
self._Personen = set()
self._Anzahl = 0
self.PvonId = {}
for P in Personen:
@@ -47,9 +46,6 @@ class Gruppe:
result = result + repr(P.Id) + ','
result = result + ' %s Person(en)' % (self._Anzahl)
return result
print self
for P in self._Personen:
print P
def Person_dazu(self, P):
self._Personen.add(P)
self._Anzahl = self._Anzahl + 1
@@ -59,16 +55,16 @@ class Gruppe:
self._Personen.remove(P)
self._Anzahl = self._Anzahl - 1
else:
raise KeyError, "Person ist nicht enthalten"
raise KeyError("Person ist nicht enthalten")
def teilen(self, NeueGroesse=0):
if self._Anzahl == 1:
raise IndexError, "Gruppe ist nur noch einer"
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 xrange(NeueGroesse):
for i in range(NeueGroesse):
two._Personen.add(one._Personen.pop())
one._Anzahl = len(one._Personen)
two._Anzahl = len(two._Personen)
@@ -82,17 +78,17 @@ class Gruppe:
def __iter__(self):
for P in self._Personen:
yield(P)
def __cmp__(self,other):
if self._Anzahl < other._Anzahl: return -1
elif self._Anzahl == other._Anzahl: return 0
else: return 1
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
raise AttributeError(attrname)
class Tisch(Gruppe):
"""enthalt alle Daten zu einem Tisch"""
@@ -103,7 +99,7 @@ class Tisch(Gruppe):
self.Nummer = Nummer
self.X_Koordinate = Koordinaten[0]
self.Y_Koordinate = Koordinaten[1]
self.Nachbarn = Set(Nachbarliste)
self.Nachbarn = set(Nachbarliste)
Gruppe.__init__( self, Id, pids )
def istvoll(self, n=0):
if ((self._Anzahl + n) == self._Plaetze ):
@@ -126,12 +122,12 @@ class Tisch(Gruppe):
return self._Anzahl
def Person_dazu(self, P):
if (self._Anzahl == self._Plaetze ):
raise IndexError, "Tisch voll"
raise IndexError("Tisch voll")
else:
Gruppe.Person_dazu( self, P )
def Person_weg(self, P):
if (self._Anzahl == 0 ):
raise IndexError, "Tisch schon leer"
raise IndexError("Tisch schon leer")
else:
Gruppe.Person_weg( self, P )
def teilen(self):
@@ -154,14 +150,14 @@ class Tisch(Gruppe):
elif attrname == 'Id':
return self.Id
else:
raise AttributeError, attrname
raise AttributeError(attrname)
# Containerklassen Personen, Gruppen, Tische, Plaetze
class Personen:
"""ein Pool mit mehreren Personen"""
def __init__( self, Personen=[] ):
self._Personen = Set(Personen)
self._Personen = set(Personen)
self.PvonId = {}
for P in Personen:
self.PvonId[ P.Id ] = P
@@ -192,7 +188,7 @@ class Gruppen:
self.NLeute = 0
for G in chain( GruppenListe ):
self.Gruppe_dazu( G )
print "gruppen hinzugefuegt"
print("gruppen hinzugefuegt")
def __iter__( self):
sortiert = self.GruppenListe[:]
sortiert.sort()
@@ -254,8 +250,8 @@ class Tische:
for T in sortiert:
yield(T)
def laden( self, FileName ):
## print "-- Tische laden"
config = ConfigParser.ConfigParser()
## print("-- Tische laden")
config = configparser.ConfigParser()
config.read( os.path.expanduser(FileName) )
for section in config.sections():
Tid = int(section)
@@ -289,13 +285,13 @@ class Plaetze:
self.__Groesster = Tische.Groesster
self.__Tische = Tische
for i in range( self.__Groesster + 1 ):
self.__TIDsNFreie.append( Set() )
self.__TIDsNFreie.append( set() )
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 xrange(self.__Groesster+1):
for i in range(self.__Groesster+1):
result = result + " " +repr(i) + " :" + repr(self.__TIDsNFreie[i]) + "\n"
return result
def IdsnFreie(self, n_freie):
@@ -381,33 +377,33 @@ class Strafliste:
self.NichtNachbar,
self.Allein) )
def gibPunkte(self, Typ, Gruppengroesse=0, N_Teilungen=0, freiePlaetze=0):
if cmp(Typ,'allein') == 0:
if Typ == 'allein':
return self.Allein
elif cmp(Typ,'nichtNachbar')== 0:
elif Typ == 'nichtNachbar':
return self.NichtNachbar
elif cmp(Typ,'Tischbesetzung')== 0:
elif Typ == 'Tischbesetzung':
try:
return self.TischWert[freiePlaetze]
except KeyError:
return 0
elif cmp(Typ,'Trennung')== 0:
elif Typ == 'Trennung':
if (N_Teilungen == 0):
return 0
elif (N_Teilungen == 1) & (Gruppengroesse < 2):
raise IndexError, "Gruppe sind mindestens zwei Leute"
raise IndexError("Gruppe sind mindestens zwei Leute")
elif (N_Teilungen == 2) & (Gruppengroesse < 3):
raise IndexError, "mindestens drei Leute noetig"
raise IndexError("mindestens drei Leute noetig")
elif (N_Teilungen > 2):
return -50
if self.GruppenTrennen[N_Teilungen].has_key(Gruppengroesse):
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"
raise IndexError("Bezeichner gibts nicht")
def laden( self, FileName ):
print "-- Strafliste laden"
config = ConfigParser.ConfigParser()
print("-- Strafliste laden")
config = configparser.ConfigParser()
config.read( os.path.expanduser(FileName) )
self.NichtNachbar = eval(config.get( 'Globals', 'NichtNachbar' ))
self.Allein = eval(config.get( 'Globals', 'Allein' ))
@@ -417,6 +413,7 @@ class Strafliste:
# Sitzplatzverteilung, bestehend aus den Einzelelementen
@total_ordering
class Sitzplatzverteilung:
"""Loesungsvorschlag fuer eine Sitzplatzordung"""
@@ -432,11 +429,11 @@ class Sitzplatzverteilung:
- zuerst alle kleinsten Gruppen, da man die nicht mehr teilen
sollte
"""
#print "+ Sitzplatzverteilung\n"
#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"
print("Anzahl der Leute:", Gruppen.NLeute)
print("Anzahl der Plaetze:", Tische.NStuehle)
raise IndexError("zu wenig Stuehle")
self.MutationsGroesse = 2
self.VIP = VIPs
@@ -464,7 +461,7 @@ class Sitzplatzverteilung:
# die Sitzplatzverteilung bepunkten
self.bewerten()
print "erzeugt: ", self.value
print("erzeugt: ", self.value)
def __repr__(self):
"""Ausgabe der Sitzplatzverteilung an der Konsole"""
r = "-- Sitzplatzverteilung:\n"
@@ -546,18 +543,18 @@ class Sitzplatzverteilung:
self.GruppenSetzen( SchlechteGruppen )
self.bewerten()
print "mutiert: ", self.value
print("mutiert: ", self.value)
return self
def __cmp__(self,other):
if self.value < other.value: return -1
elif self.value == other.value: return 0
else: return 1
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
#print(T)
n_freie = T.freiePlaetze()
Strafpunkte = self.Strafen.gibPunkte( 'Tischbesetzung', freiePlaetze=n_freie )
self.TischWertSet( T, Strafpunkte )
@@ -610,7 +607,7 @@ class Sitzplatzverteilung:
# uns setzte sie auf freie Plaetze
while len(Gruppenliste) > 0:
wahl = choice(['grosse', 'kleine'])
if cmp(wahl,'grosse')== 0:
if wahl == 'grosse':
G = Gruppenliste.pop(0)
else:
G = Gruppenliste.pop(-1)
@@ -671,7 +668,7 @@ class Sitzplatzverteilung:
Ids = self.Gid_Tid[Gruppe.Id]
Ids.add(Tisch.Id)
except KeyError:
s = Set()
s = set()
s.add(Tisch.Id)
self.Gid_Tid[Gruppe.Id] = s
def GruppeEntfernen(self, Gruppe):
@@ -751,12 +748,16 @@ class Sitzplatzverteilung:
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.iteritems():
for Gid, Anzahl in Anzahl_von_Gid.items():
if Anzahl == 1:
Gids.append(Gid)
return Gids
@@ -779,7 +780,7 @@ class Sitzplatzverteilung:
def VIPsSetzen(self, VIPListe, VIPs, Tische ):
"""setze die VIPs auf Ihre Plaetze"""
#print " VIPsSetzen:"
for PersonId, TischId in VIPListe.iteritems():
for PersonId, TischId in VIPListe.items():
#print " VIPsSetzen: PersonId", PersonId
#print " VIPsSetzen: TischId", TischId
Person = VIPs.PersonVonPid(PersonId)
@@ -791,7 +792,7 @@ class Sitzplatzverteilung:
RListe=[]
Wertigkeit=[]
# TODO filter verwenden
for Id, Wert in Liste.iteritems():
for Id, Wert in Liste.items():
if Wert <= Min:
RListe.append(Id)
Wertigkeit.append(Wert)
@@ -830,13 +831,13 @@ class XMLConfig:
GN = Gruppen()
VIPGruppe = Gruppe( 0 )
print '-HandleBestellung'
print('-HandleBestellung')
AlleGruppenXML = Bestellung.getElementsByTagName("Gruppe")
PID = 0
GID = 0
for GruppeXML in AlleGruppenXML:
GID = GID + 1
print '-- GID ' + repr(GID)
print('-- GID ' + repr(GID))
G = Gruppe( GID )
IsVip = False
@@ -844,11 +845,11 @@ class XMLConfig:
for Node in GruppeXML.childNodes:
if Node.nodeName == 'Tisch':
TischNr = self.getText(Node.childNodes)
print 'TischNr:', TischNr
print('TischNr:', TischNr)
IsVip = True
if Node.nodeName == 'Anzahl':
AnzahlReservierungen = int(self.getText(Node.childNodes))
print 'Anzahl der Reservierungen:', AnzahlReservierungen
print('Anzahl der Reservierungen:', AnzahlReservierungen)
IsContingent = True
for Node in GruppeXML.childNodes:
@@ -857,7 +858,7 @@ class XMLConfig:
for i in range(int(AnzahlReservierungen)):
NRes = NRes + 1
PID = PID + 1
print "PID =" + repr(PID)
print("PID =" + repr(PID))
info = self.handlePersonNode( Node )
Vorname = info["Vorname"]
@@ -871,15 +872,15 @@ class XMLConfig:
else:
PN.PersonAdd( P )
G.Person_dazu( P )
if (G.Anzahl > 0) & (IsVip != True):
if (G.Anzahl() > 0) & (IsVip != True):
GN.Gruppe_dazu( G )
if IsVip == True:
GID = GID - 1
print PN
print GN
print VIPGruppe
print VipHash
print '=========='
print(PN)
print(GN)
print(VIPGruppe)
print(VipHash)
print('==========')
return [ PN, GN, VIPGruppe, VipHash ]
def laden(self, Bestellung):
"""lese alle Eingangsdaten aus einem XML File ein"""
@@ -891,113 +892,113 @@ class XMLConfig:
if __name__ == '__main__':
print "\n-- Erzeuge Personen:"
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(P1)
print(P2)
print(P3)
print(P4)
print(P5)
print(P6)
print "\n-- Mehrzahl von Personen:"
print("\n-- Mehrzahl von Personen:")
PN = Personen([P1, P2, P3, P4, P5, P6])
print PN
print(PN)
print "\n-- Erzeuge Gruppen mit den Personen:"
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-- Stelle Gruppen dar:")
print(G1)
print(G2)
print(G3)
print "\n-- Durchlaufe Gruppe 3:"
print("\n-- Durchlaufe Gruppe 3:")
for P in G3:
print P
print(P)
print "\n-- Mache Liste der Gruppen:"
print("\n-- Mache Liste der Gruppen:")
GN = Gruppen([G1,G2,G3])
print GN
print(GN)
print "\n-- Durchlaufe Gruppen sortiert:"
print("\n-- Durchlaufe Gruppen sortiert:")
for G in GN:
print G
print(G)
print "\n-- Teile Gruppe:"
print("\n-- Teile Gruppe:")
[GX, GY] = G1.teilen()
print GX
print GY
print(GX)
print(GY)
[GX, GY] = G1.teilen(3)
print GX
print GY
print(GX)
print(GY)
print "\n-- Erzeuge Tische mit Personen:"
print("\n-- Erzeuge Tische mit Personen:")
T1 = Tisch( 1, Plaetze=5, Koordinaten=(1,2), pids=[P1, P2], Nachbarliste=[2] )
print T1
print(T1)
T2 = Tisch( 2, Plaetze=4, Koordinaten=(3,2), Nachbarliste=[1,3] )
T2.Person_dazu( P3 )
print T2
print(T2)
T2.Person_dazu( P4 )
print T2
print(T2)
T2.Person_weg( P4 )
print T2
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-- 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-- 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
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(TE)
print "\n-- Hole Tisch 2 aufgrund seiner Id :"
print("\n-- Hole Tisch 2 aufgrund seiner Id :")
TX = TE.vonId( 2 )
print TX
print(TX)
print "\n-- Hole Gruppe 2 aufgrund ihrer Id :"
print("\n-- Hole Gruppe 2 aufgrund ihrer Id :")
GX = GN.vonId( 2 )
print GX
print(GX)
print "\n-- Hole Person 2 aufgrund ihrer Id aus den Gruppen:"
print("\n-- Hole Person 2 aufgrund ihrer Id aus den Gruppen:")
PX = GN.PersonVonPid( 2 )
print PX
print(PX)
print "\n-- schau wo noch mindestens 3 Plaetze frei sind:"
print("\n-- schau wo noch mindestens 3 Plaetze frei sind:")
info = Plaetze(TE)
print info.mindestensNFreie( 3 )
print(info.mindestensNFreie( 3 ))
print "\n-- schau wo zwei Gruppen bestimmter Groesse nebeneinander sitzen koennen:"
print " zwei neben drei:"
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:"
print(TX)
print(TY)
print(" drei neben zwei:")
[TX,TY] = info.NachbarTische(3, 2)
print TX
print TY
print(TX)
print(TY)
P1 = Person( 1, 'Michael', 'Stangl')
P2 = Person( 2, 'Magnus', 'Mueller')
@@ -1020,27 +1021,27 @@ if __name__ == '__main__':
# G1 G2 G3 G4
# <3> <2> <2> <6>
#
print "\n-- Mache Gruppen:"
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(GN)
print "\n-- Mache Tische:"
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(TE)
print "\n-- Mache Listen:"
print("\n-- Mache Listen:")
# Personenid - Tischid
VIPListePT = { 3:1, 2:1, 1:1 }
print "VIP Liste:", VIPListePT
print("VIP Liste:", VIPListePT)
# Strafliste
# die Teilung einer grossen Gruppe ist nicht so schlimm
@@ -1053,40 +1054,36 @@ if __name__ == '__main__':
# 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(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)
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)
print("SL.gibPunkte('Trennung',(1,1):",SL.gibPunkte('Trennung',1,1))
except IndexError:
print "Gruppe sind mindestens zwei"
print("Gruppe sind mindestens zwei")
print "\n-- Baue Sitzplatzverteilung:"
print("\n-- Baue Sitzplatzverteilung:")
SV = Sitzplatzverteilung(Tische=TE, Gruppen=GN, Strafen=SL,
VIPListe=VIPListePT, VIPs=VIPGruppe)
print SV
print(SV)
print "\n-- Mutiere Sitzplatzverteilung:"
print("\n-- Mutiere Sitzplatzverteilung:")
SV.mutieren()
print SV
print(SV)
print "\n-- Gibt die 3 Tische mit der schlechtesten Wertung:"
print("\n-- Gibt die 3 Tische mit der schlechtesten Wertung:")
[TischIds, Wertigkeit]=SV.SchlechteTischeTopX(3)
print "TischIds:", TischIds
print "Wertigkeit:", Wertigkeit
print("TischIds:", TischIds)
print("Wertigkeit:", Wertigkeit)
print "\n-- Gibt die Gruppen mit der schlechtesten Wertung:"
print("\n-- Gibt die Gruppen mit der schlechtesten Wertung:")
[GruppenIds, Wertigkeit]=SV.SchlechteGruppenTopX(3)
print "GruppenIds:", GruppenIds
print "Wertigkeit:", Wertigkeit
print("GruppenIds:", GruppenIds)
print("Wertigkeit:", Wertigkeit)
else:
pass
+45 -46
View File
@@ -4,9 +4,9 @@ Enthaelt alle Datenstrukturen um die einen genetischen Algorithmus darzustellen
"""
from random import *
from itertools import chain
from string import split
from copy import deepcopy
import ConfigParser, os
from functools import total_ordering
import configparser, os
__author__ = "Michael Stangl"
@@ -15,29 +15,30 @@ __date__ = "$Date: $"
__copyright__ = "Copyright (c) 2005 Michael Stangl"
__license__ = "Python"
@total_ordering
class Loesung:
"""ein einfaches Loesungsobjekt. Taugt um es als Superklasse zu ueberladen"""
def __init__(self, *Argumente, **keywords):
self.Id = -1 * randint(1,100)
self.value = self.Id
print " initialisiere Loesung " + repr(self.Id)
print(" initialisiere Loesung " + repr(self.Id))
def mutieren(self):
print " mutiere Loesung " + repr(self.Id)
print(" mutiere Loesung " + repr(self.Id))
self.Id = -1 * randint(1,100)
self.value = self.Id
return self
def kreuzen(self,obj):
print " kreuze Loesung " + repr(self.Id)
print(" kreuze Loesung " + repr(self.Id))
def laden(self):
print " lade Loesung " + repr(self.Id)
print(" lade Loesung " + repr(self.Id))
def speichern(self):
print " speichere Loesung " + repr(self.Id)
print(" speichere Loesung " + repr(self.Id))
def __repr__(self):
return ' L '+ repr(self.Id)
def __cmp__(self,other):
if self.value < other.value: return -1
elif self.value == other.value: return 0
else: return 1
def __eq__(self,other):
return self.value == other.value
def __lt__(self,other):
return self.value < other.value
class Zyklus:
"""Definiert wie ein Zyklus ablaufen soll:
@@ -49,8 +50,8 @@ class Zyklus:
Abfolge='e,m,s',
Anzahl='10,10,10'):
self.Name = Name
self.Abfolge = split(Abfolge, ',')
self.Anzahl = split(Anzahl,',')
self.Abfolge = Abfolge.split(',')
self.Anzahl = Anzahl.split(',')
def __repr__(self):
return( "Zyklus: %s\n"
" Zyklusabfolge: %s\n"
@@ -59,7 +60,7 @@ class Zyklus:
self.Abfolge,
self.Anzahl ) )
def laden(self, FileName, ZyklusName):
config = ConfigParser.ConfigParser()
config = configparser.ConfigParser()
ZyklusPath= os.path.split( FileName )
ZyklusPath= os.path.join( FileName )
config.read( ZyklusPath )
@@ -102,50 +103,50 @@ class Farm:
pass
def LoesungenErzeugen(self, N, Klasse, *Arguments, **keywords):
""" Erzeuge Loesungen"""
for i in xrange(N):
for i in range(N):
L = Klasse( *Arguments, **keywords )
self.Pool.append(L)
#print repr(len(self.Pool)) + ' Loesungsobjekte erzeugt'
#print(repr(len(self.Pool)) + ' Loesungsobjekte erzeugt')
def LoesungenKreuzen(self,N):
"""mache aus zwei Loesungen eine neue"""
#print "+ Loesungen kreuzen"
for i in xrange(N):
#print("+ Loesungen kreuzen")
for i in range(N):
self.Pool.append(self.Pool[i].kreuzen())
def LoesungenMutieren(self, N):
"""veraendere die Loesung, so dass sie hoffentlich besser wird"""
print "+ Loesungen mutieren: ", N
for n in xrange(N):
#print " -"+repr(n+1)+" mal"
print("+ Loesungen mutieren: ", N)
for n in range(N):
#print(" -"+repr(n+1)+" mal")
for L in chain(self.Pool):
O = deepcopy(L)
self.PoolNeu.append(O.mutieren())
#print self
#print(self)
def LoesungenBehalten(self):
"""Behalte alle Eltern aus dem alten Pool"""
print "+ alten Loesungspool behalten"
print("+ alten Loesungspool behalten")
self.PoolNeu = self.PoolNeu + self.Pool
#print self
#print(self)
def NeueGeneration(self):
"""Loesche alle bisherigen Eltern und mache einen neuen Zyklus"""
print "+ Neue Generation erzeugen"
print("+ Neue Generation erzeugen")
self.Pool = self.PoolNeu
self.PoolNeu = []
#print self
#print "---------------"
#print(self)
#print("---------------")
def LoesungenAuswaehlen(self, N):
"""Selektiere die besten Loesungen"""
print "+ die besten -", repr(N) +" - Loesungen auswaehlen -"
for i in xrange(N):
print("+ die besten -", repr(N) +" - Loesungen auswaehlen -")
for i in range(N):
L = max(self.Pool)
self.PoolNeu.append(L)
self.Pool.remove(L)
#print self
#print(self)
def LoesungenZufaelligAuswaehlen(self, N):
"""Selektiere zufaellig Loesungen"""
print "+ zufaellig auswaehlen -"+ repr(N) + "-"
for i in xrange(N):
print("+ zufaellig auswaehlen -"+ repr(N) + "-")
for i in range(N):
self.PoolNeu.append(choice(self.Pool))
#print self
#print(self)
def __repr__(self):
"""Drucke die Farm am Bildschirm aus"""
return( " gewaehlte Objekte (%d): %s\n"
@@ -153,15 +154,15 @@ class Farm:
% ( len(self.PoolNeu), self.PoolNeu,
len(self.Pool), self.Pool))
def laden(self):
print "+ laden"
print("+ laden")
def speichern(self):
print "+ speichern"
print("+ speichern")
def Bester(self):
return max(self.Pool)
if __name__ == '__main__':
print "\n-- Erzeuge Zyklus:"
print("\n-- Erzeuge Zyklus:")
#Z = Zyklus( Name='Easy',
#Abfolge='e,s,+,m,j,S,+',
#Anzahl='10,5,x,2,x,5,x'
@@ -170,25 +171,23 @@ if __name__ == '__main__':
Abfolge='e,'+'s,z,m,j,z,'*3+'s',
Anzahl='10,'+'5,x,2,x,x,'*3+'1'
)
print Z
print(Z)
print "\n-- Erzeuge Loesungen:"
print("\n-- Erzeuge Loesungen:")
L1 = Loesung()
L2 = Loesung()
L3 = Loesung()
print L1, L2, L3
print 'L1 > L2 =', L1 > L2
print 'L2 > L3 =', L2 > L3
print 'L1 > L3 =', L1 > L3
print(L1, L2, L3)
print('L1 > L2 =', L1 > L2)
print('L2 > L3 =', L2 > L3)
print('L1 > L3 =', L1 > L3)
A=2
B=3
print "\n-- Erzeuge Farm:"
print("\n-- Erzeuge Farm:")
F1 = Farm( Z, Loesung, Arg1=A, Arg2=B, Arg3=L1 )
print F1
print(F1)
else:
pass
+12 -12
View File
@@ -11,7 +11,7 @@ __license__ = "Python"
from ga import *
from Strukturdaten import *
import ConfigParser
import configparser
import optparse
@@ -33,7 +33,7 @@ if __name__ == '__main__':
parser.error( "--indir '%s' ist kein Verzeichnis" % InDir )
# lade Vorgaben fuer Optimierungszyklus und
config = ConfigParser.ConfigParser()
config = configparser.ConfigParser()
ConfigPath= os.path.join( os.getenv('PLATZ_CFG'), 'platz.cfg')
config.read( os.path.expandvars( ConfigPath ))
@@ -55,26 +55,26 @@ if __name__ == '__main__':
Z = Zyklus()
Z.laden( Zyklusconfig, Zyklusart )
print Z
print(Z)
SL = Strafliste()
SL.laden( Strafenconfig )
print SL
print(SL)
TE = Tische()
TE.laden( Tischconfig )
print TE
print(TE)
InputFile = XMLConfig()
[ PN, GN, VIPGruppe, VIPListePT ] = InputFile.laden( Bestellung )
print PN
print GN
print VIPGruppe
print VIPListePT
print(PN)
print(GN)
print(VIPGruppe)
print(VIPListePT)
print "\n-- Erzeuge Farm:"
print("\n-- Erzeuge Farm:")
F1 = Farm( Z, Sitzplatzverteilung, Tische=TE, Gruppen=GN, Strafen=SL, VIPListe=VIPListePT, VIPs=VIPGruppe )
#print F1
#print(F1)
S = F1.Bester()
print S
print(S)
S.speichern( AusgabeTP, AusgabePT )
else:
+1 -1
View File
@@ -1,7 +1,7 @@
# Python-Abhaengigkeiten fuer platz
#
# Aktuell werden nur Module aus der Standardbibliothek verwendet
# (ConfigParser, xml.dom.minidom, sets, UserList, random, ...).
# (configparser, xml.dom.minidom, random, ...).
# Keine externen Pakete erforderlich.
#
# Beispiel:
+168
View File
@@ -0,0 +1,168 @@
"""
Unittests fuer Sitzplatzverteilung anhand von drei einfachen Szenarien:
1. Drei Tische in einer Kette (je 2 Plaetze), drei Paare
-> jedes Paar passt exakt an einen Tisch, keine Gruppe wird getrennt.
2. Vier Tische in einer Linie (je 2 Plaetze), zwei Vierergruppen
-> jede Vierergruppe verteilt sich auf ein zusammenhaengendes
Tischpaar (T1+T2 bzw. T3+T4), nie ueber die mittlere Naht hinweg.
3. Sechs Tische im Kreis (je 2 Plaetze), vier Dreiergruppen
-> jede Dreiergruppe wird genau einmal auf zwei Nachbartische
aufgeteilt (2+1), da kein Tisch drei Plaetze hat.
"""
import os
import random
import sys
import unittest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'libs'))
from Strukturdaten import Gruppe, Gruppen, Person, Sitzplatzverteilung, Strafliste, Tisch, Tische
def erzeuge_personen(anzahl, start_id=1):
return [Person(i, 'Vorname%d' % i, 'Nachname%d' % i) for i in range(start_id, start_id + anzahl)]
class TestDreiTischeKettePaare(unittest.TestCase):
"""Drei Tische paarweise benachbart (Kette T1-T2-T3), je 2 Plaetze, 3 Paare."""
def setUp(self):
random.seed(1)
self.T1 = Tisch(1, Plaetze=2, Koordinaten=(1, 1), Nachbarliste=[2])
self.T2 = Tisch(2, Plaetze=2, Koordinaten=(2, 1), Nachbarliste=[1, 3])
self.T3 = Tisch(3, Plaetze=2, Koordinaten=(3, 1), Nachbarliste=[2])
self.Tische = Tische([self.T1, self.T2, self.T3])
Personen = erzeuge_personen(6)
self.G1 = Gruppe(1, Personen[0:2])
self.G2 = Gruppe(2, Personen[2:4])
self.G3 = Gruppe(3, Personen[4:6])
self.Gruppen = Gruppen([self.G1, self.G2, self.G3])
self.Strafen = Strafliste(
GruppenTrennen={1: {2: -10}, 2: {}, 3: {}},
NichtNachbar=-3, Allein=-30, TischWert={1: -3, 2: -2} )
def test_alle_paare_komplett_an_einem_tisch(self):
SV = Sitzplatzverteilung( Tische=self.Tische, Gruppen=self.Gruppen,
Strafen=self.Strafen, VIPListe={}, VIPs=Gruppe(0) )
for G in (self.G1, self.G2, self.G3):
self.assertEqual( SV.GruppenTeilungGet(G), 0,
"Paar %s haette nicht getrennt werden duerfen" % G.Id )
# jeder der drei Tische ist von genau einem Paar voll besetzt
Tid_zu_Gid = {}
for Gid, Tids in SV.Gid_Tid.items():
self.assertEqual( len(Tids), 1 )
Tid_zu_Gid[next(iter(Tids))] = Gid
self.assertEqual( set(Tid_zu_Gid.keys()), {1, 2, 3} )
# optimale Verteilung: keine Strafpunkte
self.assertEqual( SV.value, 0 )
class TestVierTischeLinieViergruppen(unittest.TestCase):
"""Vier Tische in einer Linie (T1-T2-T3-T4), je 2 Plaetze, 2 Vierergruppen."""
def setUp(self):
random.seed(1)
self.T1 = Tisch(1, Plaetze=2, Koordinaten=(1, 1), Nachbarliste=[2])
self.T2 = Tisch(2, Plaetze=2, Koordinaten=(2, 1), Nachbarliste=[1, 3])
self.T3 = Tisch(3, Plaetze=2, Koordinaten=(3, 1), Nachbarliste=[2, 4])
self.T4 = Tisch(4, Plaetze=2, Koordinaten=(4, 1), Nachbarliste=[3])
self.Tische = Tische([self.T1, self.T2, self.T3, self.T4])
Personen = erzeuge_personen(8)
self.G1 = Gruppe(1, Personen[0:4])
self.G2 = Gruppe(2, Personen[4:8])
self.Gruppen = Gruppen([self.G1, self.G2])
self.Strafen = Strafliste(
GruppenTrennen={1: {4: -6}, 2: {}, 3: {}},
NichtNachbar=-3, Allein=-30, TischWert={1: -3, 2: -2} )
def test_viergruppen_bleiben_auf_zusammenhaengendem_tischpaar(self):
SV = Sitzplatzverteilung( Tische=self.Tische, Gruppen=self.Gruppen,
Strafen=self.Strafen, VIPListe={}, VIPs=Gruppe(0) )
# jede Gruppe wird genau einmal geteilt (2 Tische a 2 Plaetze)
self.assertEqual( SV.GruppenTeilungGet(self.G1), 1 )
self.assertEqual( SV.GruppenTeilungGet(self.G2), 1 )
Tids_G1 = SV.Gid_Tid[self.G1.Id]
Tids_G2 = SV.Gid_Tid[self.G2.Id]
# jede Gruppe liegt auf genau einem der beiden erwarteten Nachbarpaare,
# nie ueber die mittlere Naht (T2-T3) hinweg vermischt
erwartete_paare = ({1, 2}, {3, 4})
self.assertIn( Tids_G1, erwartete_paare )
self.assertIn( Tids_G2, erwartete_paare )
self.assertNotEqual( Tids_G1, Tids_G2 )
# alle Tische sind voll besetzt, es sitzt niemand allein
# (SV.Tische ist eine Kopie der urspruenglichen Tische, siehe
# Sitzplatzverteilung.__init__ -> Tische.deepcopy())
for T in SV.Tische:
self.assertTrue( T.istvoll() )
class TestSechsTischeKreisDreiergruppen(unittest.TestCase):
"""Sechs Tische im Kreis (T1..T6), je 2 Plaetze, 4 Dreiergruppen."""
def setUp(self):
random.seed(2)
self.T1 = Tisch(1, Plaetze=2, Koordinaten=(0, 2), Nachbarliste=[2, 6])
self.T2 = Tisch(2, Plaetze=2, Koordinaten=(1, 1), Nachbarliste=[1, 3])
self.T3 = Tisch(3, Plaetze=2, Koordinaten=(1, -1), Nachbarliste=[2, 4])
self.T4 = Tisch(4, Plaetze=2, Koordinaten=(0, -2), Nachbarliste=[3, 5])
self.T5 = Tisch(5, Plaetze=2, Koordinaten=(-1, -1), Nachbarliste=[4, 6])
self.T6 = Tisch(6, Plaetze=2, Koordinaten=(-1, 1), Nachbarliste=[5, 1])
self.Tische = Tische([self.T1, self.T2, self.T3, self.T4, self.T5, self.T6])
self.NachbarnVonTid = {
1: {2, 6}, 2: {1, 3}, 3: {2, 4}, 4: {3, 5}, 5: {4, 6}, 6: {5, 1} }
Personen = erzeuge_personen(12)
self.G1 = Gruppe(1, Personen[0:3])
self.G2 = Gruppe(2, Personen[3:6])
self.G3 = Gruppe(3, Personen[6:9])
self.G4 = Gruppe(4, Personen[9:12])
self.Gruppen = Gruppen([self.G1, self.G2, self.G3, self.G4])
self.Strafen = Strafliste(
GruppenTrennen={1: {3: -8}, 2: {}, 3: {}},
NichtNachbar=-3, Allein=-30, TischWert={1: -3, 2: -2} )
def test_dreiergruppen_je_einmal_auf_nachbartische_geteilt(self):
SV = Sitzplatzverteilung( Tische=self.Tische, Gruppen=self.Gruppen,
Strafen=self.Strafen, VIPListe={}, VIPs=Gruppe(0) )
# 4 Dreiergruppen = 12 Personen auf 6x2 = 12 Plaetzen -> alle Tische voll.
# (SV.Tische ist eine Kopie der urspruenglichen Tische, siehe
# Sitzplatzverteilung.__init__ -> Tische.deepcopy())
for T in SV.Tische:
self.assertTrue( T.istvoll() )
# da kein Tisch 3 Plaetze hat, muss jede Dreiergruppe genau einmal
# auf zwei Tische aufgeteilt werden (2+1); dies gilt unabhaengig
# vom Zufalls-Seed, da bei voller Auslastung (12 Personen auf
# 12 Plaetzen) keine andere Aufteilung uebrig bleibt.
for G in (self.G1, self.G2, self.G3, self.G4):
self.assertEqual( SV.GruppenTeilungGet(G), 1 )
Tids = SV.Gid_Tid[G.Id]
self.assertEqual( len(Tids), 2 )
# mit dem hier fest gewaehlten Seed sitzen alle geteilten Gruppen
# zusaetzlich tatsaechlich an zwei benachbarten Tischen (der
# GA-Fallback duerfte bei knapper Auslastung im Kreis im
# Allgemeinen auch nicht-benachbarte Tische waehlen)
for G in (self.G1, self.G2, self.G3, self.G4):
Tid_a, Tid_b = tuple(SV.Gid_Tid[G.Id])
self.assertIn( Tid_b, self.NachbarnVonTid[Tid_a],
"Tische %s der Gruppe %s sind keine Nachbarn" % (SV.Gid_Tid[G.Id], G.Id) )
if __name__ == '__main__':
unittest.main()