Loesungs-Dedup: getGruppenId/getPersonenId, keine doppelten Sitzordnungen
- getGruppenId (Gruppen-Ebene, personen-unabhaengig: welche Gruppen mit welcher Anzahl je Tisch) und getPersonenId (Personen-Ebene: alphabetische Namen je Tisch) an Sitzplatzverteilung; Basis-Methoden an Loesung. Gleiche Gruppen-Verteilung -> gleiche getGruppenId und damit gleiche Wertigkeit, egal welche Personen die geteilten Plaetze einnehmen. - Farm verwirft nach jeder Erzeugung/Mutation Loesungen mit schon gesehener getGruppenId, so dass dieselbe Sitzordnung nicht mehrfach gehalten und bewertet wird (defensiv: Klassen ohne getGruppenId bleiben unberuehrt, Tests unveraendert). - Web: Varianten-Vergleich ueber getGruppenId statt der person-basierten _Signatur; gruppenId und personenId zusaetzlich im Ergebnis-JSON. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ from random import *
|
|||||||
import ast
|
import ast
|
||||||
import configparser, os
|
import configparser, os
|
||||||
import copy
|
import copy
|
||||||
|
import hashlib
|
||||||
import html
|
import html
|
||||||
import math
|
import math
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@@ -1114,6 +1115,44 @@ class Sitzplatzverteilung:
|
|||||||
return len( self.Gid_Tid[Gruppe.Id] ) - 1
|
return len( self.Gid_Tid[Gruppe.Id] ) - 1
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return 0
|
return 0
|
||||||
|
def getGruppenId(self):
|
||||||
|
"""eindeutige Id der Loesung auf GRUPPEN-Ebene (personen-unabhaengig).
|
||||||
|
|
||||||
|
Signatur: je Tisch, welche Gruppen mit wie vielen Mitgliedern dort
|
||||||
|
sitzen. Zwei Loesungen mit identischer Gruppen-Verteilung bekommen
|
||||||
|
dieselbe Id - egal welche konkreten Personen die geteilten Plaetze
|
||||||
|
einnehmen (die Teilung einer 5er-Gruppe auf die Tische 3 und 4 ist
|
||||||
|
dieselbe Sitzordnung, gleich welche Personen wohin kommen). Da die
|
||||||
|
Bewertung (Strafliste) nur von den Gruppen-Anzahlen je Tisch, den
|
||||||
|
Nachbarschaften und der Auslastung abhaengt, haben Loesungen mit
|
||||||
|
gleicher getGruppenId auch dieselbe Wertigkeit. Diese Id dient dem
|
||||||
|
Vergleich der Loesungen (Dedup), damit dieselbe Sitzordnung nicht
|
||||||
|
mehrfach als eigene Loesung gehalten und bewertet wird."""
|
||||||
|
Teile = []
|
||||||
|
for T in sorted( self.Tische, key=lambda t: t.Id ):
|
||||||
|
Zaehler = {}
|
||||||
|
for P in T:
|
||||||
|
try:
|
||||||
|
Gid = self.Gruppen.GruppeVonPid( P.Id ).Id
|
||||||
|
except KeyError:
|
||||||
|
Gid = -1 # VIP / feste Platzierung (ohne GA-Gruppe)
|
||||||
|
Zaehler[Gid] = Zaehler.get( Gid, 0 ) + 1
|
||||||
|
Teile.append( (T.Id, tuple(sorted( Zaehler.items() ))) )
|
||||||
|
return hashlib.sha1( repr(Teile).encode('utf-8') ).hexdigest()[:16]
|
||||||
|
def getPersonenId(self):
|
||||||
|
"""eindeutige Id der Loesung auf PERSONEN-Ebene.
|
||||||
|
|
||||||
|
Signatur: je Tisch die alphabetisch sortierten Personennamen.
|
||||||
|
Identifiziert WIRKLICH identische Sitzbelegungen (dieselben Personen
|
||||||
|
an denselben Tischen) - feiner als getGruppenId, das nur die Gruppen-
|
||||||
|
Verteilung vergleicht."""
|
||||||
|
Teile = []
|
||||||
|
for T in sorted( self.Tische, key=lambda t: t.Id ):
|
||||||
|
Namen = sorted(
|
||||||
|
' '.join( s for s in (P._Titel, P._Vorname, P._Name) if s )
|
||||||
|
for P in T )
|
||||||
|
Teile.append( (T.Id, tuple(Namen)) )
|
||||||
|
return hashlib.sha1( repr(Teile).encode('utf-8') ).hexdigest()[:16]
|
||||||
def VIPsSetzen(self, VIPListe, VIPs, Tische ):
|
def VIPsSetzen(self, VIPListe, VIPs, Tische ):
|
||||||
"""setze die VIPs auf Ihre Plaetze"""
|
"""setze die VIPs auf Ihre Plaetze"""
|
||||||
#print " VIPsSetzen:"
|
#print " VIPsSetzen:"
|
||||||
|
|||||||
+32
-1
@@ -61,6 +61,15 @@ class Loesung:
|
|||||||
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 getGruppenId(self):
|
||||||
|
"""Id fuer den Dedup-Vergleich (Basis: jede Loesung eigenstaendig).
|
||||||
|
Unterklassen ueberladen dies mit einer inhaltlichen Signatur, so
|
||||||
|
dass gleiche Loesungen dieselbe Id bekommen und nicht mehrfach
|
||||||
|
gehalten werden."""
|
||||||
|
return id(self)
|
||||||
|
def getPersonenId(self):
|
||||||
|
"""feinere Id fuer wirklich identische Loesungen (Basis: eigenstaendig)"""
|
||||||
|
return id(self)
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ' L '+ repr(self.Id)
|
return ' L '+ repr(self.Id)
|
||||||
def __eq__(self,other):
|
def __eq__(self,other):
|
||||||
@@ -493,6 +502,9 @@ class Farm:
|
|||||||
self.Abstammung = Abstammung()
|
self.Abstammung = Abstammung()
|
||||||
self._AbsZaehler = 0 # vergibt fortlaufende Loesungs-Ids 1..N
|
self._AbsZaehler = 0 # vergibt fortlaufende Loesungs-Ids 1..N
|
||||||
self._Generation = 0
|
self._Generation = 0
|
||||||
|
# schon gesehene Loesungs-Signaturen (getGruppenId), damit dieselbe
|
||||||
|
# Sitzordnung nicht mehrfach gehalten und bewertet wird.
|
||||||
|
self._GeseheneGruppenIds = set()
|
||||||
if getattr(Zyklus, 'Modus', 'statisch') == 'adaptiv':
|
if getattr(Zyklus, 'Modus', 'statisch') == 'adaptiv':
|
||||||
self._LaufAdaptiv(Klasse, *Arguments, **keywords)
|
self._LaufAdaptiv(Klasse, *Arguments, **keywords)
|
||||||
else:
|
else:
|
||||||
@@ -580,10 +592,27 @@ class Farm:
|
|||||||
self._AbsZaehler = self._AbsZaehler + 1
|
self._AbsZaehler = self._AbsZaehler + 1
|
||||||
return self._AbsZaehler
|
return self._AbsZaehler
|
||||||
|
|
||||||
|
def _LoesungIstNeu(self, L):
|
||||||
|
"""Kontrolle nach jeder Erzeugung: True, wenn die Loesung eine bisher
|
||||||
|
ungesehene Sitzordnung hat (Vergleich ueber getGruppenId, also auf
|
||||||
|
Gruppen-Ebene) - und merkt sie dann vor. Duplikate (gleiche Sitz-
|
||||||
|
ordnung) werden so nicht noch einmal gehalten und bewertet.
|
||||||
|
Loesungen ohne getGruppenId gelten immer als neu (kein Dedup)."""
|
||||||
|
Hole = getattr(L, 'getGruppenId', None)
|
||||||
|
if Hole is None:
|
||||||
|
return True
|
||||||
|
Gid = Hole()
|
||||||
|
if Gid in self._GeseheneGruppenIds:
|
||||||
|
return False
|
||||||
|
self._GeseheneGruppenIds.add(Gid)
|
||||||
|
return True
|
||||||
|
|
||||||
def LoesungenErzeugen(self, N, Klasse, *Arguments, **keywords):
|
def LoesungenErzeugen(self, N, Klasse, *Arguments, **keywords):
|
||||||
""" Erzeuge Loesungen"""
|
""" Erzeuge Loesungen (bereits gesehene Sitzordnungen werden verworfen)"""
|
||||||
for i in range(N):
|
for i in range(N):
|
||||||
L = Klasse( *Arguments, **keywords )
|
L = Klasse( *Arguments, **keywords )
|
||||||
|
if not self._LoesungIstNeu(L):
|
||||||
|
continue # gleiche Sitzordnung schon vorhanden -> nicht behalten
|
||||||
L._absid = self._NeueId() # Wurzel: kein Vorgaenger
|
L._absid = self._NeueId() # Wurzel: kein Vorgaenger
|
||||||
self.Abstammung.erfassen(
|
self.Abstammung.erfassen(
|
||||||
L._absid, None, self._Generation, getattr(L, 'value', 0), 'e' )
|
L._absid, None, self._Generation, getattr(L, 'value', 0), 'e' )
|
||||||
@@ -603,6 +632,8 @@ class Farm:
|
|||||||
for L in chain(self.Pool):
|
for L in chain(self.Pool):
|
||||||
O = deepcopy(L)
|
O = deepcopy(L)
|
||||||
Kind = O.mutieren()
|
Kind = O.mutieren()
|
||||||
|
if not self._LoesungIstNeu(Kind):
|
||||||
|
continue # gleiche Sitzordnung schon gesehen -> verwerfen
|
||||||
# Kind bekommt eine neue fortlaufende Nummer; der Vorgaenger
|
# Kind bekommt eine neue fortlaufende Nummer; der Vorgaenger
|
||||||
# (Eltern-Id) wird separat in der Abstammung gemerkt.
|
# (Eltern-Id) wird separat in der Abstammung gemerkt.
|
||||||
ElternId = getattr(L, '_absid', None)
|
ElternId = getattr(L, '_absid', None)
|
||||||
|
|||||||
+9
-15
@@ -153,16 +153,6 @@ def _TischeBauen(S):
|
|||||||
return TE
|
return TE
|
||||||
|
|
||||||
|
|
||||||
def _Signatur(SV):
|
|
||||||
"""Sitzordnungs-Signatur einer Loesung: welche Personen an welchem Tisch.
|
|
||||||
Zwei Loesungen mit gleicher Signatur sind fuer den Nutzer dieselbe
|
|
||||||
Sitzordnung (der GA-Pool enthaelt viele Mutations-Verwandte) — die
|
|
||||||
Varianten-Auswahl dedupliziert damit (Roadmap M2: 'unterschiedliche
|
|
||||||
gute Loesungen')."""
|
|
||||||
return frozenset(
|
|
||||||
( T.Id, frozenset( P.Id for P in T ) ) for T in SV.Tische )
|
|
||||||
|
|
||||||
|
|
||||||
def _VarianteJSON(SV, GruppenNamen, Nr):
|
def _VarianteJSON(SV, GruppenNamen, Nr):
|
||||||
"""baut die JSON-Darstellung EINER Loesung (eine Variante)"""
|
"""baut die JSON-Darstellung EINER Loesung (eine Variante)"""
|
||||||
def GruppeVonPerson(P):
|
def GruppeVonPerson(P):
|
||||||
@@ -217,6 +207,8 @@ def _VarianteJSON(SV, GruppenNamen, Nr):
|
|||||||
return {
|
return {
|
||||||
'nr': Nr,
|
'nr': Nr,
|
||||||
'wertigkeit': SV.value,
|
'wertigkeit': SV.value,
|
||||||
|
'gruppenId': SV.getGruppenId(),
|
||||||
|
'personenId': SV.getPersonenId(),
|
||||||
'kennzahlen': Kennzahlen,
|
'kennzahlen': Kennzahlen,
|
||||||
'tische': TischListe,
|
'tische': TischListe,
|
||||||
'gruppen': GruppenListe,
|
'gruppen': GruppenListe,
|
||||||
@@ -471,13 +463,15 @@ def Berechnen(SitzungsId: str,
|
|||||||
|
|
||||||
# Roadmap M2: nicht nur den Besten, sondern bis zu 'varianten'
|
# Roadmap M2: nicht nur den Besten, sondern bis zu 'varianten'
|
||||||
# UNTERSCHIEDLICHE gute Loesungen aus dem Endpool — Farm.TopN liefert
|
# UNTERSCHIEDLICHE gute Loesungen aus dem Endpool — Farm.TopN liefert
|
||||||
# absteigend sortiert, die Signatur-Deduplizierung wirft Loesungen mit
|
# absteigend sortiert. Der Vergleich der Loesungen laeuft ueber die
|
||||||
# identischer Sitzordnung raus (Mutations-Verwandte). Liefert der Pool
|
# getGruppenId (Gruppen-Ebene): dieselbe Sitzordnung zaehlt nicht mehrfach
|
||||||
# weniger unterschiedliche Sitzordnungen, kommen eben weniger zurueck
|
# als eigene Variante. Der GA-Pool ist ohnehin schon per getGruppenId
|
||||||
# (ein weiterer Klick auf Berechnen erzeugt einen frischen Lauf).
|
# dedupliziert; das hier ist die zusaetzliche Absicherung fuer die Ausgabe.
|
||||||
|
# Liefert der Pool weniger unterschiedliche Sitzordnungen, kommen eben
|
||||||
|
# weniger zurueck (ein weiterer Klick auf Berechnen erzeugt einen frischen Lauf).
|
||||||
Ergebnisse, Gesehen = [], set()
|
Ergebnisse, Gesehen = [], set()
|
||||||
for SV in GAFarm.TopN( len( GAFarm.Pool ) ):
|
for SV in GAFarm.TopN( len( GAFarm.Pool ) ):
|
||||||
Sig = _Signatur( SV )
|
Sig = SV.getGruppenId()
|
||||||
if Sig in Gesehen:
|
if Sig in Gesehen:
|
||||||
continue
|
continue
|
||||||
Gesehen.add( Sig )
|
Gesehen.add( Sig )
|
||||||
|
|||||||
Reference in New Issue
Block a user