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 table layout, runs a small custom GA framework to search for a good seating, and writes the
result to text/CSV output files. result to text/CSV output files.
**This is Python 2 code** (print statements, `ConfigParser`, `sets.Set`, `UserList`, **This is Python 3 code**, migrated from an original Python 2 implementation (the git
`string.split`, old-style `raise Exception, "msg"`, `xrange`, `dict.has_key`, history / old comments may still say "Python 2" — that's now stale). Notable migration
`dict.iteritems`). It will not run under Python 3 without a 2to3-style port. There is no artifacts to be aware of when touching this code:
build system, test suite, or linter configured in this repo. `requirements.txt` exists but - `Gruppe`, `Loesung`, and `Sitzplatzverteilung` use `@functools.total_ordering` with
is empty/commented — everything used is standard library — it's there only so `__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`. `bin/install_py.*` has something to install into the `.venv`.
## Running the program ## 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 "$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). `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`, `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 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 receives the output files — e.g. `work/test1/`, `work/test2/`, or any new folder following
the same layout: the same layout:
- `tische.ini` — table definitions: id, `Nummer`, `Hof` (venue/court), `Plaetze` (seats), - `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 - `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 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 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 ### `Strukturdaten.py` — domain model for this specific seating problem
- `Person`, `Gruppe` (group — a `Set` of people, splittable via `teilen()`), `Tisch` (table — - `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 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. 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`, **This is Python 3 code** (migrated from an original Python 2 implementation). There are no
`string.split`, old-style `raise Exception, "msg"`, `xrange`, `dict.has_key`, third-party dependencies (standard library only), so `requirements.txt` is empty — it
`dict.iteritems`). There are no third-party dependencies (standard library only), so exists only so `bin/install_py` has something to install.
`requirements.txt` is empty — it exists only so `bin/install_py` has something to install.
## Project structure ## Project structure
@@ -17,6 +16,7 @@ platz/
├── bin/ # environment + launcher scripts (.bat + .sh pairs) ├── bin/ # environment + launcher scripts (.bat + .sh pairs)
├── cfg/ # GA cycle definition and penalty scheme ├── cfg/ # GA cycle definition and penalty scheme
├── libs/ # the two Python modules (ga.py, Strukturdaten.py, platz.py) ├── 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 └── work/ # per-run input (table layout, guest list) and output
├── test1/ ├── test1/
└── test2/ └── test2/
@@ -27,6 +27,16 @@ platz/
- `libs/Strukturdaten.py` — domain model for the seating problem (`Person`, `Gruppe`, - `libs/Strukturdaten.py` — domain model for the seating problem (`Person`, `Gruppe`,
`Tisch`, `Strafliste`, `Sitzplatzverteilung`, `XMLConfig`). `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 ## Setup
1. Install a Python interpreter (`py` on Windows / `python3` on Linux/macOS must be on 1. Install a Python interpreter (`py` on Windows / `python3` on Linux/macOS must be on
+20 -6
View File
@@ -1,8 +1,22 @@
# 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] [Easy]
Abfolge='e,'+'s,z,m,j,z,'*3+'s' Abfolge='e,'+'s,z,m,j,z,'*3+'s'
Anzahl='10,'+'5,x,2,x,x,'*3+'1' Anzahl='10,'+'5,x,2,x,x,'*3+'1'
[Simple] [Simple]
Abfolge='e,s' Abfolge='e,s'
Anzahl='20,1' Anzahl='20,1'
+193 -196
View File
@@ -9,12 +9,11 @@ __date__ = "$Date: $"
__copyright__ = "Copyright (c) 2005 Michael Stangl" __copyright__ = "Copyright (c) 2005 Michael Stangl"
__license__ = "Python" __license__ = "Python"
from sets import Set
from itertools import chain from itertools import chain
from UserList import UserList
from random import * from random import *
import ConfigParser, os import configparser, os
import copy import copy
from functools import total_ordering
from xml.dom import minidom from xml.dom import minidom
@@ -29,14 +28,14 @@ class Person:
def __repr__(self): def __repr__(self):
return ' Person %s: "%s %s %s"' % ( self.Id, self._Titel, return ' Person %s: "%s %s %s"' % ( self.Id, self._Titel,
self._Vorname, self._Name) self._Vorname, self._Name)
print self.__repr__
@total_ordering
class Gruppe: class Gruppe:
"""enthalt alle Daten zu einer Gruppe""" """enthalt alle Daten zu einer Gruppe"""
Art = 'Gruppe' Art = 'Gruppe'
def __init__(self, Id, Personen=[]): def __init__(self, Id, Personen=[]):
self.Id = Id self.Id = Id
self._Personen = Set() self._Personen = set()
self._Anzahl = 0 self._Anzahl = 0
self.PvonId = {} self.PvonId = {}
for P in Personen: for P in Personen:
@@ -47,9 +46,6 @@ class Gruppe:
result = result + repr(P.Id) + ',' result = result + repr(P.Id) + ','
result = result + ' %s Person(en)' % (self._Anzahl) result = result + ' %s Person(en)' % (self._Anzahl)
return result return result
print self
for P in self._Personen:
print P
def Person_dazu(self, P): def Person_dazu(self, P):
self._Personen.add(P) self._Personen.add(P)
self._Anzahl = self._Anzahl + 1 self._Anzahl = self._Anzahl + 1
@@ -59,16 +55,16 @@ class Gruppe:
self._Personen.remove(P) self._Personen.remove(P)
self._Anzahl = self._Anzahl - 1 self._Anzahl = self._Anzahl - 1
else: else:
raise KeyError, "Person ist nicht enthalten" raise KeyError("Person ist nicht enthalten")
def teilen(self, NeueGroesse=0): def teilen(self, NeueGroesse=0):
if self._Anzahl == 1: if self._Anzahl == 1:
raise IndexError, "Gruppe ist nur noch einer" raise IndexError("Gruppe ist nur noch einer")
if NeueGroesse == 0: if NeueGroesse == 0:
NeueGroesse = int(self._Anzahl/2) NeueGroesse = int(self._Anzahl/2)
one = Gruppe( self.Id, self._Personen ) one = Gruppe( self.Id, self._Personen )
two = Gruppe( self.Id, self._Personen ) two = Gruppe( self.Id, self._Personen )
two._Personen.clear() two._Personen.clear()
for i in xrange(NeueGroesse): for i in range(NeueGroesse):
two._Personen.add(one._Personen.pop()) two._Personen.add(one._Personen.pop())
one._Anzahl = len(one._Personen) one._Anzahl = len(one._Personen)
two._Anzahl = len(two._Personen) two._Anzahl = len(two._Personen)
@@ -78,21 +74,21 @@ class Gruppe:
def Anzahl(self): def Anzahl(self):
return self._Anzahl return self._Anzahl
def __contains__(self, P): def __contains__(self, P):
return P in self._Personen return P in self._Personen
def __iter__(self): def __iter__(self):
for P in self._Personen: for P in self._Personen:
yield(P) yield(P)
def __cmp__(self,other): def __eq__(self,other):
if self._Anzahl < other._Anzahl: return -1 return self._Anzahl == other._Anzahl
elif self._Anzahl == other._Anzahl: return 0 def __lt__(self,other):
else: return 1 return self._Anzahl < other._Anzahl
def __getattr__(self, attrname): def __getattr__(self, attrname):
if attrname == 'Anzahl': if attrname == 'Anzahl':
return self._Anzahl return self._Anzahl
elif attrname == 'Id': elif attrname == 'Id':
return self.Id return self.Id
else: else:
raise AttributeError, attrname raise AttributeError(attrname)
class Tisch(Gruppe): class Tisch(Gruppe):
"""enthalt alle Daten zu einem Tisch""" """enthalt alle Daten zu einem Tisch"""
@@ -103,7 +99,7 @@ class Tisch(Gruppe):
self.Nummer = Nummer self.Nummer = Nummer
self.X_Koordinate = Koordinaten[0] self.X_Koordinate = Koordinaten[0]
self.Y_Koordinate = Koordinaten[1] self.Y_Koordinate = Koordinaten[1]
self.Nachbarn = Set(Nachbarliste) self.Nachbarn = set(Nachbarliste)
Gruppe.__init__( self, Id, pids ) Gruppe.__init__( self, Id, pids )
def istvoll(self, n=0): def istvoll(self, n=0):
if ((self._Anzahl + n) == self._Plaetze ): if ((self._Anzahl + n) == self._Plaetze ):
@@ -126,12 +122,12 @@ class Tisch(Gruppe):
return self._Anzahl return self._Anzahl
def Person_dazu(self, P): def Person_dazu(self, P):
if (self._Anzahl == self._Plaetze ): if (self._Anzahl == self._Plaetze ):
raise IndexError, "Tisch voll" raise IndexError("Tisch voll")
else: else:
Gruppe.Person_dazu( self, P ) Gruppe.Person_dazu( self, P )
def Person_weg(self, P): def Person_weg(self, P):
if (self._Anzahl == 0 ): if (self._Anzahl == 0 ):
raise IndexError, "Tisch schon leer" raise IndexError("Tisch schon leer")
else: else:
Gruppe.Person_weg( self, P ) Gruppe.Person_weg( self, P )
def teilen(self): def teilen(self):
@@ -140,7 +136,7 @@ class Tisch(Gruppe):
result = ( Gruppe.__repr__( self ) + result = ( Gruppe.__repr__( self ) +
' fuer [%s], %s noch frei,' % ' fuer [%s], %s noch frei,' %
(self._Plaetze, (self._Plaetze,
self._Plaetze - self._Anzahl) self._Plaetze - self._Anzahl)
) )
result = result + " Nachbarn:" result = result + " Nachbarn:"
for N in chain(self.Nachbarn): for N in chain(self.Nachbarn):
@@ -148,20 +144,20 @@ class Tisch(Gruppe):
return result return result
def __getattr__(self, attrname): def __getattr__(self, attrname):
if attrname == 'Anzahl': if attrname == 'Anzahl':
return self._Anzahl return self._Anzahl
if attrname == 'Plaetze': if attrname == 'Plaetze':
return self._Plaetze return self._Plaetze
elif attrname == 'Id': elif attrname == 'Id':
return self.Id return self.Id
else: else:
raise AttributeError, attrname raise AttributeError(attrname)
# Containerklassen Personen, Gruppen, Tische, Plaetze # Containerklassen Personen, Gruppen, Tische, Plaetze
class Personen: class Personen:
"""ein Pool mit mehreren Personen""" """ein Pool mit mehreren Personen"""
def __init__( self, Personen=[] ): def __init__( self, Personen=[] ):
self._Personen = Set(Personen) self._Personen = set(Personen)
self.PvonId = {} self.PvonId = {}
for P in Personen: for P in Personen:
self.PvonId[ P.Id ] = P self.PvonId[ P.Id ] = P
@@ -192,7 +188,7 @@ class Gruppen:
self.NLeute = 0 self.NLeute = 0
for G in chain( GruppenListe ): for G in chain( GruppenListe ):
self.Gruppe_dazu( G ) self.Gruppe_dazu( G )
print "gruppen hinzugefuegt" print("gruppen hinzugefuegt")
def __iter__( self): def __iter__( self):
sortiert = self.GruppenListe[:] sortiert = self.GruppenListe[:]
sortiert.sort() sortiert.sort()
@@ -254,8 +250,8 @@ class Tische:
for T in sortiert: for T in sortiert:
yield(T) yield(T)
def laden( self, FileName ): def laden( self, FileName ):
## print "-- Tische laden" ## print("-- Tische laden")
config = ConfigParser.ConfigParser() config = configparser.ConfigParser()
config.read( os.path.expanduser(FileName) ) config.read( os.path.expanduser(FileName) )
for section in config.sections(): for section in config.sections():
Tid = int(section) Tid = int(section)
@@ -288,14 +284,14 @@ class Plaetze:
self.__TIDsNFreie = [] self.__TIDsNFreie = []
self.__Groesster = Tische.Groesster self.__Groesster = Tische.Groesster
self.__Tische = Tische self.__Tische = Tische
for i in range( self.__Groesster + 1 ): for i in range( self.__Groesster + 1 ):
self.__TIDsNFreie.append( Set() ) self.__TIDsNFreie.append( set() )
for T in Tische.TischListe: for T in Tische.TischListe:
n_freie = T.freiePlaetze() n_freie = T.freiePlaetze()
self.__TIDsNFreie[n_freie].add(T.Id) self.__TIDsNFreie[n_freie].add(T.Id)
def __repr__(self): def __repr__(self):
result = "\n-- Tische mit n Freien Plaetzen:\n" 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" result = result + " " +repr(i) + " :" + repr(self.__TIDsNFreie[i]) + "\n"
return result return result
def IdsnFreie(self, n_freie): def IdsnFreie(self, n_freie):
@@ -381,33 +377,33 @@ class Strafliste:
self.NichtNachbar, self.NichtNachbar,
self.Allein) ) self.Allein) )
def gibPunkte(self, Typ, Gruppengroesse=0, N_Teilungen=0, freiePlaetze=0): def gibPunkte(self, Typ, Gruppengroesse=0, N_Teilungen=0, freiePlaetze=0):
if cmp(Typ,'allein') == 0: if Typ == 'allein':
return self.Allein return self.Allein
elif cmp(Typ,'nichtNachbar')== 0: elif Typ == 'nichtNachbar':
return self.NichtNachbar return self.NichtNachbar
elif cmp(Typ,'Tischbesetzung')== 0: elif Typ == 'Tischbesetzung':
try: try:
return self.TischWert[freiePlaetze] return self.TischWert[freiePlaetze]
except KeyError: except KeyError:
return 0 return 0
elif cmp(Typ,'Trennung')== 0: elif Typ == 'Trennung':
if (N_Teilungen == 0): if (N_Teilungen == 0):
return 0 return 0
elif (N_Teilungen == 1) & (Gruppengroesse < 2): 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): elif (N_Teilungen == 2) & (Gruppengroesse < 3):
raise IndexError, "mindestens drei Leute noetig" raise IndexError("mindestens drei Leute noetig")
elif (N_Teilungen > 2): elif (N_Teilungen > 2):
return -50 return -50
if self.GruppenTrennen[N_Teilungen].has_key(Gruppengroesse): if Gruppengroesse in self.GruppenTrennen[N_Teilungen]:
return self.GruppenTrennen[N_Teilungen][Gruppengroesse] return self.GruppenTrennen[N_Teilungen][Gruppengroesse]
else: else:
return self.gibPunkte('Trennung',Gruppengroesse-1, N_Teilungen) return self.gibPunkte('Trennung',Gruppengroesse-1, N_Teilungen)
else: else:
raise IndexError, "Bezeichner gibts nicht" raise IndexError("Bezeichner gibts nicht")
def laden( self, FileName ): def laden( self, FileName ):
print "-- Strafliste laden" print("-- Strafliste laden")
config = ConfigParser.ConfigParser() config = configparser.ConfigParser()
config.read( os.path.expanduser(FileName) ) config.read( os.path.expanduser(FileName) )
self.NichtNachbar = eval(config.get( 'Globals', 'NichtNachbar' )) self.NichtNachbar = eval(config.get( 'Globals', 'NichtNachbar' ))
self.Allein = eval(config.get( 'Globals', 'Allein' )) self.Allein = eval(config.get( 'Globals', 'Allein' ))
@@ -417,6 +413,7 @@ class Strafliste:
# Sitzplatzverteilung, bestehend aus den Einzelelementen # Sitzplatzverteilung, bestehend aus den Einzelelementen
@total_ordering
class Sitzplatzverteilung: class Sitzplatzverteilung:
"""Loesungsvorschlag fuer eine Sitzplatzordung""" """Loesungsvorschlag fuer eine Sitzplatzordung"""
@@ -432,11 +429,11 @@ class Sitzplatzverteilung:
- zuerst alle kleinsten Gruppen, da man die nicht mehr teilen - zuerst alle kleinsten Gruppen, da man die nicht mehr teilen
sollte sollte
""" """
#print "+ Sitzplatzverteilung\n" #print("+ Sitzplatzverteilung\n")
if Gruppen.NLeute > Tische.NStuehle: if Gruppen.NLeute > Tische.NStuehle:
print "Anzahl der Leute:", Gruppen.NLeute print("Anzahl der Leute:", Gruppen.NLeute)
print "Anzahl der Plaetze:", Tische.NStuehle print("Anzahl der Plaetze:", Tische.NStuehle)
raise IndexError, "zu wenig Stuehle" raise IndexError("zu wenig Stuehle")
self.MutationsGroesse = 2 self.MutationsGroesse = 2
self.VIP = VIPs self.VIP = VIPs
@@ -463,8 +460,8 @@ class Sitzplatzverteilung:
self.GruppenSetzen( Gruppen ) self.GruppenSetzen( Gruppen )
# die Sitzplatzverteilung bepunkten # die Sitzplatzverteilung bepunkten
self.bewerten() self.bewerten()
print "erzeugt: ", self.value print("erzeugt: ", self.value)
def __repr__(self): def __repr__(self):
"""Ausgabe der Sitzplatzverteilung an der Konsole""" """Ausgabe der Sitzplatzverteilung an der Konsole"""
r = "-- Sitzplatzverteilung:\n" r = "-- Sitzplatzverteilung:\n"
@@ -544,20 +541,20 @@ class Sitzplatzverteilung:
self.GruppeEntfernen(G) self.GruppeEntfernen(G)
SchlechteGruppen.append(G) SchlechteGruppen.append(G)
self.GruppenSetzen( SchlechteGruppen ) self.GruppenSetzen( SchlechteGruppen )
self.bewerten() self.bewerten()
print "mutiert: ", self.value print("mutiert: ", self.value)
return self return self
def __cmp__(self,other): def __eq__(self,other):
if self.value < other.value: return -1 return self.value == other.value
elif self.value == other.value: return 0 def __lt__(self,other):
else: return 1 return self.value < other.value
def bewerten(self): def bewerten(self):
"""Errechnet die momentane Wertigkeit der Sitzplaetze""" """Errechnet die momentane Wertigkeit der Sitzplaetze"""
self.GesamtWertSet( 0 ) self.GesamtWertSet( 0 )
# Tische bewerten # Tische bewerten
for T in self.Tische: for T in self.Tische:
#print T #print(T)
n_freie = T.freiePlaetze() n_freie = T.freiePlaetze()
Strafpunkte = self.Strafen.gibPunkte( 'Tischbesetzung', freiePlaetze=n_freie ) Strafpunkte = self.Strafen.gibPunkte( 'Tischbesetzung', freiePlaetze=n_freie )
self.TischWertSet( T, Strafpunkte ) self.TischWertSet( T, Strafpunkte )
@@ -610,7 +607,7 @@ class Sitzplatzverteilung:
# uns setzte sie auf freie Plaetze # uns setzte sie auf freie Plaetze
while len(Gruppenliste) > 0: while len(Gruppenliste) > 0:
wahl = choice(['grosse', 'kleine']) wahl = choice(['grosse', 'kleine'])
if cmp(wahl,'grosse')== 0: if wahl == 'grosse':
G = Gruppenliste.pop(0) G = Gruppenliste.pop(0)
else: else:
G = Gruppenliste.pop(-1) G = Gruppenliste.pop(-1)
@@ -671,8 +668,8 @@ class Sitzplatzverteilung:
Ids = self.Gid_Tid[Gruppe.Id] Ids = self.Gid_Tid[Gruppe.Id]
Ids.add(Tisch.Id) Ids.add(Tisch.Id)
except KeyError: except KeyError:
s = Set() s = set()
s.add(Tisch.Id) s.add(Tisch.Id)
self.Gid_Tid[Gruppe.Id] = s self.Gid_Tid[Gruppe.Id] = s
def GruppeEntfernen(self, Gruppe): def GruppeEntfernen(self, Gruppe):
"""entfernt eine Gruppe von Ihren Tischen""" """entfernt eine Gruppe von Ihren Tischen"""
@@ -751,16 +748,20 @@ class Sitzplatzverteilung:
Anzahl_von_Gid = {} Anzahl_von_Gid = {}
Gids = [] Gids = []
for P in Tisch: for P in Tisch:
G = self.Gruppen.GruppeVonPid(P.Id)
try: try:
Anzahl_von_Gid[G.Id] = Anzahl_von_Gid[G.Id] + 1 G = self.Gruppen.GruppeVonPid(P.Id)
except KeyError: except KeyError:
Anzahl_von_Gid[G.Id] = 1 # Person gehoert zu keiner Gruppe in self.Gruppen (z.B. VIP)
for Gid, Anzahl in Anzahl_von_Gid.iteritems(): continue
try:
Anzahl_von_Gid[G.Id] = Anzahl_von_Gid[G.Id] + 1
except KeyError:
Anzahl_von_Gid[G.Id] = 1
for Gid, Anzahl in Anzahl_von_Gid.items():
if Anzahl == 1: if Anzahl == 1:
Gids.append(Gid) Gids.append(Gid)
return Gids return Gids
# Funktionen fuer mehrere Einheiten (Information) # Funktionen fuer mehrere Einheiten (Information)
def SchlechteTischeTopX(self, Anzahl=-1): def SchlechteTischeTopX(self, Anzahl=-1):
@@ -779,7 +780,7 @@ class Sitzplatzverteilung:
def VIPsSetzen(self, VIPListe, VIPs, Tische ): def VIPsSetzen(self, VIPListe, VIPs, Tische ):
"""setze die VIPs auf Ihre Plaetze""" """setze die VIPs auf Ihre Plaetze"""
#print " VIPsSetzen:" #print " VIPsSetzen:"
for PersonId, TischId in VIPListe.iteritems(): for PersonId, TischId in VIPListe.items():
#print " VIPsSetzen: PersonId", PersonId #print " VIPsSetzen: PersonId", PersonId
#print " VIPsSetzen: TischId", TischId #print " VIPsSetzen: TischId", TischId
Person = VIPs.PersonVonPid(PersonId) Person = VIPs.PersonVonPid(PersonId)
@@ -791,7 +792,7 @@ class Sitzplatzverteilung:
RListe=[] RListe=[]
Wertigkeit=[] Wertigkeit=[]
# TODO filter verwenden # TODO filter verwenden
for Id, Wert in Liste.iteritems(): for Id, Wert in Liste.items():
if Wert <= Min: if Wert <= Min:
RListe.append(Id) RListe.append(Id)
Wertigkeit.append(Wert) Wertigkeit.append(Wert)
@@ -830,13 +831,13 @@ class XMLConfig:
GN = Gruppen() GN = Gruppen()
VIPGruppe = Gruppe( 0 ) VIPGruppe = Gruppe( 0 )
print '-HandleBestellung' print('-HandleBestellung')
AlleGruppenXML = Bestellung.getElementsByTagName("Gruppe") AlleGruppenXML = Bestellung.getElementsByTagName("Gruppe")
PID = 0 PID = 0
GID = 0 GID = 0
for GruppeXML in AlleGruppenXML: for GruppeXML in AlleGruppenXML:
GID = GID + 1 GID = GID + 1
print '-- GID ' + repr(GID) print('-- GID ' + repr(GID))
G = Gruppe( GID ) G = Gruppe( GID )
IsVip = False IsVip = False
@@ -844,42 +845,42 @@ class XMLConfig:
for Node in GruppeXML.childNodes: for Node in GruppeXML.childNodes:
if Node.nodeName == 'Tisch': if Node.nodeName == 'Tisch':
TischNr = self.getText(Node.childNodes) TischNr = self.getText(Node.childNodes)
print 'TischNr:', TischNr print('TischNr:', TischNr)
IsVip = True IsVip = True
if Node.nodeName == 'Anzahl': if Node.nodeName == 'Anzahl':
AnzahlReservierungen = int(self.getText(Node.childNodes)) AnzahlReservierungen = int(self.getText(Node.childNodes))
print 'Anzahl der Reservierungen:', AnzahlReservierungen print('Anzahl der Reservierungen:', AnzahlReservierungen)
IsContingent = True IsContingent = True
for Node in GruppeXML.childNodes: for Node in GruppeXML.childNodes:
if Node.nodeName == 'Person': if Node.nodeName == 'Person':
NRes = 0 NRes = 0
for i in range(int(AnzahlReservierungen)): for i in range(int(AnzahlReservierungen)):
NRes = NRes + 1 NRes = NRes + 1
PID = PID + 1 PID = PID + 1
print "PID =" + repr(PID) print("PID =" + repr(PID))
info = self.handlePersonNode( Node ) info = self.handlePersonNode( Node )
Vorname = info["Vorname"] Vorname = info["Vorname"]
Nachname = info["Nachname"] Nachname = info["Nachname"]
Titel = info["Titel"] Titel = info["Titel"]
P = Person( PID, Vorname, Nachname, Titel) P = Person( PID, Vorname, Nachname, Titel)
if IsVip == True: if IsVip == True:
PidListe.append( int(PID) ) PidListe.append( int(PID) )
VipHash[ int(PID) ] = int(TischNr) VipHash[ int(PID) ] = int(TischNr)
VIPGruppe.Person_dazu( P ) VIPGruppe.Person_dazu( P )
else: else:
PN.PersonAdd( P ) PN.PersonAdd( P )
G.Person_dazu( P ) G.Person_dazu( P )
if (G.Anzahl > 0) & (IsVip != True): if (G.Anzahl() > 0) & (IsVip != True):
GN.Gruppe_dazu( G ) GN.Gruppe_dazu( G )
if IsVip == True: if IsVip == True:
GID = GID - 1 GID = GID - 1
print PN print(PN)
print GN print(GN)
print VIPGruppe print(VIPGruppe)
print VipHash print(VipHash)
print '==========' print('==========')
return [ PN, GN, VIPGruppe, VipHash ] return [ PN, GN, VIPGruppe, VipHash ]
def laden(self, Bestellung): def laden(self, Bestellung):
"""lese alle Eingangsdaten aus einem XML File ein""" """lese alle Eingangsdaten aus einem XML File ein"""
@@ -891,113 +892,113 @@ class XMLConfig:
if __name__ == '__main__': if __name__ == '__main__':
print "\n-- Erzeuge Personen:" print("\n-- Erzeuge Personen:")
P1 = Person( 1, 'Michael', 'Stangl') P1 = Person( 1, 'Michael', 'Stangl')
P2 = Person( 2, 'Magnus', 'Mueller') P2 = Person( 2, 'Magnus', 'Mueller')
P3 = Person( 3, 'Bernd Robert', 'Hoehn') P3 = Person( 3, 'Bernd Robert', 'Hoehn')
P4 = Person( 4, 'Heinz', 'Ruehmann') P4 = Person( 4, 'Heinz', 'Ruehmann')
P5 = Person( 5, 'Franz', 'Bruegge') P5 = Person( 5, 'Franz', 'Bruegge')
P6 = Person( 6, 'Hubert', 'K') P6 = Person( 6, 'Hubert', 'K')
print P1 print(P1)
print P2 print(P2)
print P3 print(P3)
print P4 print(P4)
print P5 print(P5)
print P6 print(P6)
print "\n-- Mehrzahl von Personen:" print("\n-- Mehrzahl von Personen:")
PN = Personen([P1, P2, P3, P4, P5, P6]) 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 = Gruppe( 1, [P1, P2] )
G1.Person_dazu( P3 ) G1.Person_dazu( P3 )
G2 = Gruppe( 2 ) G2 = Gruppe( 2 )
G2.Person_dazu( P4 ) G2.Person_dazu( P4 )
G3 = Gruppe( 3, [P5,P6] ) G3 = Gruppe( 3, [P5,P6] )
print "\n-- Stelle Gruppen dar:" print("\n-- Stelle Gruppen dar:")
print G1 print(G1)
print G2 print(G2)
print G3 print(G3)
print "\n-- Durchlaufe Gruppe 3:" print("\n-- Durchlaufe Gruppe 3:")
for P in G3: 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]) GN = Gruppen([G1,G2,G3])
print GN print(GN)
print "\n-- Durchlaufe Gruppen sortiert:" print("\n-- Durchlaufe Gruppen sortiert:")
for G in GN: for G in GN:
print G print(G)
print "\n-- Teile Gruppe:" print("\n-- Teile Gruppe:")
[GX, GY] = G1.teilen() [GX, GY] = G1.teilen()
print GX print(GX)
print GY print(GY)
[GX, GY] = G1.teilen(3) [GX, GY] = G1.teilen(3)
print GX print(GX)
print GY 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] ) 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 = Tisch( 2, Plaetze=4, Koordinaten=(3,2), Nachbarliste=[1,3] )
T2.Person_dazu( P3 ) T2.Person_dazu( P3 )
print T2 print(T2)
T2.Person_dazu( P4 ) T2.Person_dazu( P4 )
print T2 print(T2)
T2.Person_weg( P4 ) T2.Person_weg( P4 )
print T2 print(T2)
T3 = Tisch( 3, Plaetze=2, Koordinaten=(5,2), Nachbarliste=[2] ) T3 = Tisch( 3, Plaetze=2, Koordinaten=(5,2), Nachbarliste=[2] )
print "\n-- Ist Tisch 2 voll oder leer?:" print("\n-- Ist Tisch 2 voll oder leer?:")
print "ist voll = ", T2.istvoll() print("ist voll = ", T2.istvoll())
print "ist leer = ", T2.istleer() print("ist leer = ", T2.istleer())
print "\n-- Wieviele Plaetze sind an Tisch 1 noch frei ?:" print("\n-- Wieviele Plaetze sind an Tisch 1 noch frei ?:")
print "Freie Plaetze = ", T1.freiePlaetze() print("Freie Plaetze = ", T1.freiePlaetze())
print "Besetzte Plaetze = ", T1.besetztePlaetze() print("Besetzte Plaetze = ", T1.besetztePlaetze())
print "\n-- Stelle Tische dar:" print("\n-- Stelle Tische dar:")
print T1 print(T1)
print T2 print(T2)
print T3 print(T3)
print "\n-- Ist Person Nummer 3 an Tisch 1 ?:" print("\n-- Ist Person Nummer 3 an Tisch 1 ?:")
print P1 in T1 print(P1 in T1)
print "-- Ist Person Nummer 4 an Tisch 1 ?:" print("-- Ist Person Nummer 4 an Tisch 1 ?:")
print P4 in T1 print(P4 in T1)
TE = Tische([T1,T2,T3]) 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 ) TX = TE.vonId( 2 )
print TX 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("\n-- Hole Gruppe 2 aufgrund ihrer Id :")
print " zwei neben drei:" 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) [TX,TY] = info.NachbarTische(2, 3)
print TX print(TX)
print TY print(TY)
print " drei neben zwei:" print(" drei neben zwei:")
[TX,TY] = info.NachbarTische(3, 2) [TX,TY] = info.NachbarTische(3, 2)
print TX print(TX)
print TY print(TY)
P1 = Person( 1, 'Michael', 'Stangl') P1 = Person( 1, 'Michael', 'Stangl')
P2 = Person( 2, 'Magnus', 'Mueller') P2 = Person( 2, 'Magnus', 'Mueller')
@@ -1013,37 +1014,37 @@ if __name__ == '__main__':
P12 = Person( 12, 'Bla', 'Button') P12 = Person( 12, 'Bla', 'Button')
P13 = Person( 13, 'Heinz Harald', 'Frenzen') P13 = Person( 13, 'Heinz Harald', 'Frenzen')
# #
# T1 T2 T3 T4 # T1 T2 T3 T4
# <3> <5> <3> <4> # <3> <5> <3> <4>
# #
# G1 G2 G3 G4 # G1 G2 G3 G4
# <3> <2> <2> <6> # <3> <2> <2> <6>
# #
print "\n-- Mache Gruppen:" print("\n-- Mache Gruppen:")
VIPGruppe = ( Gruppe( 1, [P1, P2, P3] ) ) VIPGruppe = ( Gruppe( 1, [P1, P2, P3] ) )
G2 = ( Gruppe( 2, [P4, P5] ) ) G2 = ( Gruppe( 2, [P4, P5] ) )
G3 = ( Gruppe( 3, [P6, P7] ) ) G3 = ( Gruppe( 3, [P6, P7] ) )
G4 = ( Gruppe( 4, [P8, P9, P10, P11, P12, P13] ) ) G4 = ( Gruppe( 4, [P8, P9, P10, P11, P12, P13] ) )
GN = Gruppen([G2,G3,G4]) 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] ) ) T1 = ( Tisch( 1, Plaetze=3, Koordinaten=(1,1), Nachbarliste=[2] ) )
T2 = ( Tisch( 2, Plaetze=5, Koordinaten=(2,1), Nachbarliste=[1,3] ) ) T2 = ( Tisch( 2, Plaetze=5, Koordinaten=(2,1), Nachbarliste=[1,3] ) )
T3 = ( Tisch( 3, Plaetze=3, Koordinaten=(3,1), Nachbarliste=[2,4] ) ) T3 = ( Tisch( 3, Plaetze=3, Koordinaten=(3,1), Nachbarliste=[2,4] ) )
T4 = ( Tisch( 4, Plaetze=4, Koordinaten=(4,1), Nachbarliste=[3] ) ) T4 = ( Tisch( 4, Plaetze=4, Koordinaten=(4,1), Nachbarliste=[3] ) )
TE = Tische([T1,T2,T3,T4]) TE = Tische([T1,T2,T3,T4])
print TE print(TE)
print "\n-- Mache Listen:" print("\n-- Mache Listen:")
# Personenid - Tischid # Personenid - Tischid
VIPListePT = { 3:1, 2:1, 1:1 } VIPListePT = { 3:1, 2:1, 1:1 }
print "VIP Liste:", VIPListePT print("VIP Liste:", VIPListePT)
# Strafliste # Strafliste
# die Teilung einer grossen Gruppe ist nicht so schlimm # die Teilung einer grossen Gruppe ist nicht so schlimm
# wie die einer kleinen # wie die einer kleinen
Teilung = list() Teilung = list()
Teilung.append({'x':'x'}) Teilung.append({'x':'x'})
@@ -1053,40 +1054,36 @@ if __name__ == '__main__':
# Ein Platz an einem Tisch frei ist schlechter als zwei Plaetze frei # Ein Platz an einem Tisch frei ist schlechter als zwei Plaetze frei
TischWertigkeiten={ 1:-3, 2:-2, 3:-1 } TischWertigkeiten={ 1:-3, 2:-2, 3:-1 }
SL = Strafliste(GruppenTrennen=Teilung, NichtNachbar=-3, Allein=-30, TischWert=TischWertigkeiten) SL = Strafliste(GruppenTrennen=Teilung, NichtNachbar=-3, Allein=-30, TischWert=TischWertigkeiten)
print SL print(SL)
print "\n-- Teste Strafliste:" print("\n-- Teste Strafliste:")
print "SL.gibPunkte('Trennung', 7,1):", SL.gibPunkte('Trennung', 7,1) 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', 5,1):", SL.gibPunkte('Trennung', 5,1))
print "SL.gibPunkte('Trennung', 3,1):", SL.gibPunkte('Trennung', 3,1) print("SL.gibPunkte('Trennung', 3,1):", SL.gibPunkte('Trennung', 3,1))
try: 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: 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, SV = Sitzplatzverteilung(Tische=TE, Gruppen=GN, Strafen=SL,
VIPListe=VIPListePT, VIPs=VIPGruppe) VIPListe=VIPListePT, VIPs=VIPGruppe)
print SV print(SV)
print "\n-- Mutiere Sitzplatzverteilung:" print("\n-- Mutiere Sitzplatzverteilung:")
SV.mutieren() 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) [TischIds, Wertigkeit]=SV.SchlechteTischeTopX(3)
print "TischIds:", TischIds print("TischIds:", TischIds)
print "Wertigkeit:", Wertigkeit 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) [GruppenIds, Wertigkeit]=SV.SchlechteGruppenTopX(3)
print "GruppenIds:", GruppenIds print("GruppenIds:", GruppenIds)
print "Wertigkeit:", Wertigkeit print("Wertigkeit:", Wertigkeit)
else: else:
pass pass
+56 -57
View File
@@ -4,9 +4,9 @@ Enthaelt alle Datenstrukturen um die einen genetischen Algorithmus darzustellen
""" """
from random import * from random import *
from itertools import chain from itertools import chain
from string import split
from copy import deepcopy from copy import deepcopy
import ConfigParser, os from functools import total_ordering
import configparser, os
__author__ = "Michael Stangl" __author__ = "Michael Stangl"
@@ -15,29 +15,30 @@ __date__ = "$Date: $"
__copyright__ = "Copyright (c) 2005 Michael Stangl" __copyright__ = "Copyright (c) 2005 Michael Stangl"
__license__ = "Python" __license__ = "Python"
@total_ordering
class Loesung: class Loesung:
"""ein einfaches Loesungsobjekt. Taugt um es als Superklasse zu ueberladen""" """ein einfaches Loesungsobjekt. Taugt um es als Superklasse zu ueberladen"""
def __init__(self, *Argumente, **keywords): def __init__(self, *Argumente, **keywords):
self.Id = -1 * randint(1,100) self.Id = -1 * randint(1,100)
self.value = self.Id self.value = self.Id
print " initialisiere Loesung " + repr(self.Id) print(" initialisiere Loesung " + repr(self.Id))
def mutieren(self): def mutieren(self):
print " mutiere Loesung " + repr(self.Id) print(" mutiere Loesung " + repr(self.Id))
self.Id = -1 * randint(1,100) self.Id = -1 * randint(1,100)
self.value = self.Id self.value = self.Id
return self return self
def kreuzen(self,obj): def kreuzen(self,obj):
print " kreuze Loesung " + repr(self.Id) print(" kreuze Loesung " + repr(self.Id))
def laden(self): def laden(self):
print " lade Loesung " + repr(self.Id) print(" lade Loesung " + repr(self.Id))
def speichern(self): def speichern(self):
print " speichere Loesung " + repr(self.Id) print(" speichere Loesung " + repr(self.Id))
def __repr__(self): def __repr__(self):
return ' L '+ repr(self.Id) return ' L '+ repr(self.Id)
def __cmp__(self,other): def __eq__(self,other):
if self.value < other.value: return -1 return self.value == other.value
elif self.value == other.value: return 0 def __lt__(self,other):
else: return 1 return self.value < other.value
class Zyklus: class Zyklus:
"""Definiert wie ein Zyklus ablaufen soll: """Definiert wie ein Zyklus ablaufen soll:
@@ -45,21 +46,21 @@ class Zyklus:
wann sollen Objekte generiert, gekreuzt, mutiert und selektiert werden wann sollen Objekte generiert, gekreuzt, mutiert und selektiert werden
wieviele sind davon jeweils betroffen wieviele sind davon jeweils betroffen
""" """
def __init__(self, Name='default', def __init__(self, Name='default',
Abfolge='e,m,s', Abfolge='e,m,s',
Anzahl='10,10,10'): Anzahl='10,10,10'):
self.Name = Name self.Name = Name
self.Abfolge = split(Abfolge, ',') self.Abfolge = Abfolge.split(',')
self.Anzahl = split(Anzahl,',') self.Anzahl = Anzahl.split(',')
def __repr__(self): def __repr__(self):
return( "Zyklus: %s\n" return( "Zyklus: %s\n"
" Zyklusabfolge: %s\n" " Zyklusabfolge: %s\n"
" Anzahl : %s\n" " Anzahl : %s\n"
% ( self.Name, % ( self.Name,
self.Abfolge, self.Abfolge,
self.Anzahl ) ) self.Anzahl ) )
def laden(self, FileName, ZyklusName): def laden(self, FileName, ZyklusName):
config = ConfigParser.ConfigParser() config = configparser.ConfigParser()
ZyklusPath= os.path.split( FileName ) ZyklusPath= os.path.split( FileName )
ZyklusPath= os.path.join( FileName ) ZyklusPath= os.path.join( FileName )
config.read( ZyklusPath ) config.read( ZyklusPath )
@@ -102,93 +103,91 @@ class Farm:
pass pass
def LoesungenErzeugen(self, N, Klasse, *Arguments, **keywords): def LoesungenErzeugen(self, N, Klasse, *Arguments, **keywords):
""" Erzeuge Loesungen""" """ Erzeuge Loesungen"""
for i in xrange(N): for i in range(N):
L = Klasse( *Arguments, **keywords ) L = Klasse( *Arguments, **keywords )
self.Pool.append(L) self.Pool.append(L)
#print repr(len(self.Pool)) + ' Loesungsobjekte erzeugt' #print(repr(len(self.Pool)) + ' Loesungsobjekte erzeugt')
def LoesungenKreuzen(self,N): def LoesungenKreuzen(self,N):
"""mache aus zwei Loesungen eine neue""" """mache aus zwei Loesungen eine neue"""
#print "+ Loesungen kreuzen" #print("+ Loesungen kreuzen")
for i in xrange(N): for i in range(N):
self.Pool.append(self.Pool[i].kreuzen()) self.Pool.append(self.Pool[i].kreuzen())
def LoesungenMutieren(self, N): def LoesungenMutieren(self, N):
"""veraendere die Loesung, so dass sie hoffentlich besser wird""" """veraendere die Loesung, so dass sie hoffentlich besser wird"""
print "+ Loesungen mutieren: ", N print("+ Loesungen mutieren: ", N)
for n in xrange(N): for n in range(N):
#print " -"+repr(n+1)+" mal" #print(" -"+repr(n+1)+" mal")
for L in chain(self.Pool): for L in chain(self.Pool):
O = deepcopy(L) O = deepcopy(L)
self.PoolNeu.append(O.mutieren()) self.PoolNeu.append(O.mutieren())
#print self #print(self)
def LoesungenBehalten(self): def LoesungenBehalten(self):
"""Behalte alle Eltern aus dem alten Pool""" """Behalte alle Eltern aus dem alten Pool"""
print "+ alten Loesungspool behalten" print("+ alten Loesungspool behalten")
self.PoolNeu = self.PoolNeu + self.Pool self.PoolNeu = self.PoolNeu + self.Pool
#print self #print(self)
def NeueGeneration(self): def NeueGeneration(self):
"""Loesche alle bisherigen Eltern und mache einen neuen Zyklus""" """Loesche alle bisherigen Eltern und mache einen neuen Zyklus"""
print "+ Neue Generation erzeugen" print("+ Neue Generation erzeugen")
self.Pool = self.PoolNeu self.Pool = self.PoolNeu
self.PoolNeu = [] self.PoolNeu = []
#print self #print(self)
#print "---------------" #print("---------------")
def LoesungenAuswaehlen(self, N): def LoesungenAuswaehlen(self, N):
"""Selektiere die besten Loesungen""" """Selektiere die besten Loesungen"""
print "+ die besten -", repr(N) +" - Loesungen auswaehlen -" print("+ die besten -", repr(N) +" - Loesungen auswaehlen -")
for i in xrange(N): for i in range(N):
L = max(self.Pool) L = max(self.Pool)
self.PoolNeu.append(L) self.PoolNeu.append(L)
self.Pool.remove(L) self.Pool.remove(L)
#print self #print(self)
def LoesungenZufaelligAuswaehlen(self, N): def LoesungenZufaelligAuswaehlen(self, N):
"""Selektiere zufaellig Loesungen""" """Selektiere zufaellig Loesungen"""
print "+ zufaellig auswaehlen -"+ repr(N) + "-" print("+ zufaellig auswaehlen -"+ repr(N) + "-")
for i in xrange(N): for i in range(N):
self.PoolNeu.append(choice(self.Pool)) self.PoolNeu.append(choice(self.Pool))
#print self #print(self)
def __repr__(self): def __repr__(self):
"""Drucke die Farm am Bildschirm aus""" """Drucke die Farm am Bildschirm aus"""
return( " gewaehlte Objekte (%d): %s\n" return( " gewaehlte Objekte (%d): %s\n"
" Elternobjekte (%d) : %s\n" " Elternobjekte (%d) : %s\n"
% ( len(self.PoolNeu), self.PoolNeu, % ( len(self.PoolNeu), self.PoolNeu,
len(self.Pool), self.Pool)) len(self.Pool), self.Pool))
def laden(self): def laden(self):
print "+ laden" print("+ laden")
def speichern(self): def speichern(self):
print "+ speichern" print("+ speichern")
def Bester(self): def Bester(self):
return max(self.Pool) return max(self.Pool)
if __name__ == '__main__': if __name__ == '__main__':
print "\n-- Erzeuge Zyklus:" print("\n-- Erzeuge Zyklus:")
#Z = Zyklus( Name='Easy', #Z = Zyklus( Name='Easy',
#Abfolge='e,s,+,m,j,S,+', #Abfolge='e,s,+,m,j,S,+',
#Anzahl='10,5,x,2,x,5,x' #Anzahl='10,5,x,2,x,5,x'
#) #)
Z = Zyklus( Name='Easy', Z = Zyklus( Name='Easy',
Abfolge='e,'+'s,z,m,j,z,'*3+'s', Abfolge='e,'+'s,z,m,j,z,'*3+'s',
Anzahl='10,'+'5,x,2,x,x,'*3+'1' Anzahl='10,'+'5,x,2,x,x,'*3+'1'
) )
print Z print(Z)
print "\n-- Erzeuge Loesungen:" print("\n-- Erzeuge Loesungen:")
L1 = Loesung() L1 = Loesung()
L2 = Loesung() L2 = Loesung()
L3 = Loesung() L3 = Loesung()
print L1, L2, L3 print(L1, L2, L3)
print 'L1 > L2 =', L1 > L2 print('L1 > L2 =', L1 > L2)
print 'L2 > L3 =', L2 > L3 print('L2 > L3 =', L2 > L3)
print 'L1 > L3 =', L1 > L3 print('L1 > L3 =', L1 > L3)
A=2 A=2
B=3 B=3
print "\n-- Erzeuge Farm:" print("\n-- Erzeuge Farm:")
F1 = Farm( Z, Loesung, Arg1=A, Arg2=B, Arg3=L1 ) F1 = Farm( Z, Loesung, Arg1=A, Arg2=B, Arg3=L1 )
print F1 print(F1)
else: else:
pass pass
+12 -12
View File
@@ -11,7 +11,7 @@ __license__ = "Python"
from ga import * from ga import *
from Strukturdaten import * from Strukturdaten import *
import ConfigParser import configparser
import optparse import optparse
@@ -33,7 +33,7 @@ if __name__ == '__main__':
parser.error( "--indir '%s' ist kein Verzeichnis" % InDir ) parser.error( "--indir '%s' ist kein Verzeichnis" % InDir )
# lade Vorgaben fuer Optimierungszyklus und # lade Vorgaben fuer Optimierungszyklus und
config = ConfigParser.ConfigParser() config = configparser.ConfigParser()
ConfigPath= os.path.join( os.getenv('PLATZ_CFG'), 'platz.cfg') ConfigPath= os.path.join( os.getenv('PLATZ_CFG'), 'platz.cfg')
config.read( os.path.expandvars( ConfigPath )) config.read( os.path.expandvars( ConfigPath ))
@@ -55,26 +55,26 @@ if __name__ == '__main__':
Z = Zyklus() Z = Zyklus()
Z.laden( Zyklusconfig, Zyklusart ) Z.laden( Zyklusconfig, Zyklusart )
print Z print(Z)
SL = Strafliste() SL = Strafliste()
SL.laden( Strafenconfig ) SL.laden( Strafenconfig )
print SL print(SL)
TE = Tische() TE = Tische()
TE.laden( Tischconfig ) TE.laden( Tischconfig )
print TE print(TE)
InputFile = XMLConfig() InputFile = XMLConfig()
[ PN, GN, VIPGruppe, VIPListePT ] = InputFile.laden( Bestellung ) [ PN, GN, VIPGruppe, VIPListePT ] = InputFile.laden( Bestellung )
print PN print(PN)
print GN print(GN)
print VIPGruppe print(VIPGruppe)
print VIPListePT print(VIPListePT)
print "\n-- Erzeuge Farm:" print("\n-- Erzeuge Farm:")
F1 = Farm( Z, Sitzplatzverteilung, Tische=TE, Gruppen=GN, Strafen=SL, VIPListe=VIPListePT, VIPs=VIPGruppe ) F1 = Farm( Z, Sitzplatzverteilung, Tische=TE, Gruppen=GN, Strafen=SL, VIPListe=VIPListePT, VIPs=VIPGruppe )
#print F1 #print(F1)
S = F1.Bester() S = F1.Bester()
print S print(S)
S.speichern( AusgabeTP, AusgabePT ) S.speichern( AusgabeTP, AusgabePT )
else: else:
+1 -1
View File
@@ -1,7 +1,7 @@
# Python-Abhaengigkeiten fuer platz # Python-Abhaengigkeiten fuer platz
# #
# Aktuell werden nur Module aus der Standardbibliothek verwendet # Aktuell werden nur Module aus der Standardbibliothek verwendet
# (ConfigParser, xml.dom.minidom, sets, UserList, random, ...). # (configparser, xml.dom.minidom, random, ...).
# Keine externen Pakete erforderlich. # Keine externen Pakete erforderlich.
# #
# Beispiel: # 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()