Compare commits

...

10 Commits

Author SHA1 Message Date
m.stangl 66a772ebb0 Add CLAUDE.md for Claude Code guidance
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 23:22:50 +02:00
m.stangl 1974e4501c „README.md“ ändern
Rechtsschreibfehler entfernt
2023-12-12 15:29:32 +00:00
m.stangl b57baf58b3 - neue Zeile rein 2023-06-13 15:34:53 +02:00
m.stangl e1ea4c4779 Initialisierung des Tuplesort implementiert 2023-05-25 16:39:57 +02:00
m.stangl 8260553612 Tuple aus Nummernfolge implementiert für eine gleichmässige Aufteilung auf beide Kreisel 2023-03-07 16:40:04 +01:00
m.stangl 82652b88a3 Klasse Numbers zur Behandlung der Tuples impl. 2023-03-06 19:06:41 +01:00
ljedlitschka 58036d4421 Python aktualisiert 2023-02-06 15:31:34 +01:00
ljedlitschka 46584930f6 Docu ergänzt und Python aktualisiert 2023-02-06 15:29:12 +01:00
ljedlitschka 91a3fbfcba temporäre Powerpoint ausgeschlossen 2023-02-06 14:44:19 +01:00
ljedlitschka 92de97e710 Videos der Verfahren erstellt 2023-02-06 14:40:22 +01:00
8 changed files with 429 additions and 85 deletions
+1 -1
View File
@@ -159,4 +159,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
*.pptx#
+34
View File
@@ -0,0 +1,34 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Projektübersicht
**SimulationTuplesort** ist ein Werkzeug zur Simulation verschiedener Sortierverfahren in
einer Anlage — **Picksort** und **Tuplesort**. Modelliert werden Kreisel (`Roundabout`),
Weichen (`Switch`) und deren Zusammenspiel; das Verhalten wird über Unittests
veranschaulicht (siehe `README.md` mit Animationen in `video/`).
## Projektstruktur
```text
SimulationTuplesort/
├── Roundabouts.py # Simulationslogik (Roundabout, Switch, Sortierverfahren)
├── doc/ # Dokumentation
├── video/ # Animationen der Verfahren (picksort.gif u. a.)
├── LICENSE
└── README.md
```
## Konzept
- **Picksort:** holt bei jeder Umdrehung das kleinstmögliche Objekt aus dem ersten
Kreisel → sortierte Liste im zweiten Kreisel.
- **Tuplesort:** alternatives Verfahren (siehe README).
## Nutzung
```cmd
python Roundabouts.py
```
Beispiel/Unittest siehe `README.md` (`Roundabout`, `Switch`).
+56 -1
View File
@@ -1,3 +1,58 @@
# SimulationTuplesort
ein Tool zur Simulation der verschiedenen Sortierverfahren in einer Anlage; picksort und tuplesort
ein Tool zur Simulation der verschiedenen Sortierverfahren in einer Anlage:
- picksort und
- tuplesort.
## Picksort-Verfahren
![Picksort-Verfahren](video/picksort.gif)
Das Picksort Verfahren holt sich bei jeder Umdrehung das kleinstmögliche Objekt aus dem ersten Kreisel heraus. Daraus ergibt sich im zweiten Kreisel eine sortierte Liste der Bügelträger
Zur Vermittelung der einzelnen Schritte wurden folgender Unittest umgesetzt:
```python
lK = Roundabout()
lK.addList([6, 5, 4, 3, 2, 1])
rK = Roundabout(1)
sw = Switch(lK, rK)
p = RoundaboutPair(lK, rK, sw)
self.assertEqual(lK.content(), [1, 2, 3, 4, 5, 6])
p.runSimulation()
self.assertEqual(p.show(), ([], [1, 2, 3, 4, 5, 6]))
```
## Tuplesort-Verfahren
![Tuplesort-Verfahren](video/tuplesort.gif)
Das Tupelsort Verfahren arbeitet mit sog. Tupels. Dies sind Zahlenfolge die immer aufsteigend sind.
Eine Zahlenfolge wie z.B. 1, 3, 5, 10, 2, 4, 7, 5, 8 kann in 3 Tupels unterteilt werden. Immer wenn sich in den Zahlen ein Sprung "nach unten" befindet, definiert man ein Tupelende. Aus dieser Zahlenfolge ergeben sich beispielsweise die 3 Tuple 1, 3, 5, 10 | 2, 4, 7 | 5, 8 .
Durch Zusammenfügen der Einzeltupel ergibt sich am Ende eine sortierte Folge.
Das Tupelsort verfahren hat bei einer größeren Anzahl von Elementen im Kreisel eine bessere Sortierperformance.
Zur Vermittelung der einzelnen Schritte wurden folgender Unittest umgesetzt:
```python
lK = Roundabout()
lK.addList([3, 2, 1])
rK = Roundabout(1)
rK.addList([6, 5, 4])
sw = Switch(lK, rK)
p = RoundaboutPair(lK, rK, sw)
sw.set_to_right()
self.assertEqual(p.show(), ([1, 2, 3], [6, 5, 4]))
p.sortStepTupleSort()
self.assertEqual(p.show(), ([1, 2], [5, 4, 3, 6]))
p.sortStepTupleSort()
self.assertEqual(p.show(), ([5, 2, 1], [4, 3, 6]))
p.sortStepTupleSort()
self.assertEqual(p.show(), ([5, 2], [3, 6, 1, 4]))
p.sortStepTupleSort()
self.assertEqual(p.show(), ([6, 5, 3, 2], [1, 4]))
p.sortStepTupleSort()
self.assertEqual(p.show(), ([], [1, 2, 3, 4, 5, 6]))
```
+338 -83
View File
@@ -1,9 +1,48 @@
import unittest
import math
LEFT = 0
RIGHT = 1
class Numbers:
def __init__(self, content:list):
self._content = content
def __repr__(self):
return str(self._content)
def isEmpty(self):
return len(self._content) == 0
def getNumTuples(self):
if self.isEmpty():
return 0
lastItem = self._content[-1]
numTuples = 1
for e in reversed(self._content):
if e < lastItem:
numTuples += 1
lastItem = e
return numTuples
def getTuples(self, toGet:int)-> list:
res = list()
if self.isEmpty():
return res
lastItem = self._content[-1]
numTuples = 1
for e in reversed(self._content):
if e < lastItem:
numTuples += 1
lastItem = e
if numTuples <= toGet:
res.insert(0, e)
else:
break
return res
class Roundabout:
def __init__(self, rotation = 0):
def __init__(self, rotation = LEFT):
self.coatHangers = list()
self.rotation = rotation
@@ -12,13 +51,13 @@ class Roundabout:
self.add(item)
def add(self, item):
if self.rotation == 0: #linksdrehend
if self.rotation == LEFT: #linksdrehend
self.coatHangers.insert(0, item)
else:
self.coatHangers.append(item)
def remove(self):
if self.rotation == 0: #linksdrehend
if self.rotation == LEFT: #linksdrehend
return self.coatHangers.pop()
else:
return self.coatHangers.pop(0)
@@ -26,8 +65,15 @@ class Roundabout:
def content(self):
return self.coatHangers
def hasItems(self):
if len(self.coatHangers) == 0:
return False
return True
def itemOnSeparator(self):
if self.rotation == 0: #linksdrehend
if len(self.coatHangers) == 0:
return None
if self.rotation == LEFT: #linksdrehend
return self.coatHangers[-1]
else:
return self.coatHangers[0]
@@ -37,7 +83,26 @@ class Roundabout:
def isEmpty(self):
return len(self.coatHangers) == 0
def clear(self):
self.coatHangers.clear()
def clear(self):
self.coatHangers.clear()
def getNumTuples(self) -> int:
content = list(self.content())
if self.rotation == RIGHT: #rechtssdrehend
content.reverse()
n = Numbers(content)
return n.getNumTuples()
def getTuples(self, numToGet:int) -> list:
content = list(self.content())
if self.rotation == RIGHT: #rechtsdrehend
content.reverse()
n = Numbers(content)
return n.getTuples(numToGet)
class Switch:
def __init__(self, left_roundabout: Roundabout, right_roundabout: Roundabout):
@@ -52,20 +117,20 @@ class Switch:
self.set_to_left()
def set_to_left(self):
self.status = 0
self.status = LEFT
return self.status
def set_to_right(self):
self.status = 1
self.status = RIGHT
return self.status
def is_left(self):
if self.status == 0:
if self.status == LEFT:
return True
return False
def is_right(self):
if self.status == 1:
if self.status == RIGHT:
return True
return False
@@ -75,74 +140,160 @@ class Switch:
else:
self.rR.add(item)
class RoundaboutPair:
def __init__(self, left_roundabout: Roundabout, right_roundabout: Roundabout, switch: Switch):
self.lR = left_roundabout
self.rR = right_roundabout
self.sw = switch
self.sortProcedure = "p" # picksort
def setSortType (self, t):
""" param t: walweise p(icksort) oder t(uplesort)
def sortStepPickSort(self):
"""Picksort Verfahren. Hole immer das kleinste aus dem Roundabout raus
"""
self.sortProcedure = t
def sortStep(self):
if self.sortProcedure == "p": # picksort
if self.lR.getSmallestItem() == self.lR.itemOnSeparator():
item = self.lR.remove()
self.sw.set_to_right()
self.sw.assign(item)
self.sw.set_to_left()
else:
item = self.lR.remove()
self.sw.assign(item)
else: # tuplesort
self.sw.set_to_right()
runSorting = True
while runSorting:
maxOfLastItems = self.passageOfSeparatorItems()
leftItem = self.lR.itemOnSeparator()
rightItem = self.rR.itemOnSeparator()
minOfCurentSeparatorItems = min(leftItem, rightItem)
if maxOfLastItems > minOfCurentSeparatorItems:
self.sw.switch()
if self.lR.isEmpty():
runSorting = False
def passageOfSeparatorItems(self):
if self.lR.itemOnSeparator() < self.rR.itemOnSeparator():
leftItem = self.lR.remove()
self.sw.assign(leftItem)
rightItem = self.rR.remove()
self.sw.assign(rightItem)
if self.lR.getSmallestItem() == self.lR.itemOnSeparator():
item = self.lR.remove()
self.sw.set_to_right() # weiche auf ausschleusen
self.sw.assign(item) # item auf die Weiche setzen
self.sw.set_to_left() # weiche wieder zurück setzen
else:
rightItem = self.rR.remove()
self.sw.assign(rightItem)
leftItem = self.lR.remove()
self.sw.assign(leftItem)
return max(rightItem, leftItem)
item = self.lR.remove()
self.sw.assign(item)
def initTupleSort(self):
if self.lR.isEmpty():
return False
nleft = Numbers(self.lR.content()).getNumTuples()
nright = Numbers(self.rR.content()).getNumTuples()
nTarget = math.ceil((nleft + nright)/2)
if (nleft+nright) % 2 == 0: # even number
addOn = 0
else:
addOn = 1
# bei ungerader Anzahl von Bügelträgern sollte der linke immer die Mehrheit besitzen
# also z.B. 5 - 4
# bei einem geradzahligen Tupel, sollten nach der Initialisierung auf beiden Seiten diesselbe Anzahl von Tupel stehen
def runSimulation(self):
number = 0
while (nleft != nright+addOn):
if nleft > nright:
before = self.doPassageLeftItem()
else:
before = self.doPassageRightItem()
nleft = Numbers(self.lR.content()).getNumTuples()
nright = Numbers(self.rR.content()).getNumTuples()
return True
def sortStepTupleSort(self):
before = 0
while True:
rightItem = self.rR.itemOnSeparator()
leftItem = self.lR.itemOnSeparator()
# wenn beide leer sind macht der Methode keinen Sinn
if rightItem == None and leftItem == None:
return
# wenn der linke Kreisel leer ist, sind wir fertig
if leftItem == None:
break
if rightItem is None:
rightItem = 0
# raus zum nächsten Schritt. Ende dieses Tupels erreicht.
if max(leftItem, rightItem) < before:
break
if leftItem < rightItem:
if leftItem > before:
before = self.doPassageLeftItem()
if rightItem > before:
before = self.doPassageRightItem()
else:
if rightItem > before:
before = self.doPassageRightItem()
if leftItem > before:
before = self.doPassageLeftItem()
# Weiche auf die andere Seite stellen
self.sw.switch()
return
def doPassageLeftItem(self):
"""greift sich das Element vom linken Roundabout und fährt durch die Weiche
"""
leftItem = self.lR.itemOnSeparator()
if leftItem == None:
return
leftItem = self.lR.remove()
self.sw.assign(leftItem)
return leftItem
def doPassageRightItem(self):
"""greift sich das Element vom rechten Roundabout und fährt durch die Weiche
"""
rightItem = self.rR.itemOnSeparator()
if rightItem == None:
return
rightItem = self.rR.remove()
self.sw.assign(rightItem)
return rightItem
def runPickSort(self):
# number = 0
while not self.lR.isEmpty():
self.sortStep()
number += 1
if number > 300:
self.sortStepPickSort()
# number += 1
# if number > 300:
# break
def runTupleSort(self):
number = 0
self.sw.set_to_right()
runSorting = True
while runSorting:
self.sortStepTupleSort()
if self.lR.isEmpty():
break
def show(self):
return (self.lR.content(), self.rR.content())
class TestObjectMethods(unittest.TestCase):
def test_order(self):
# tupel werden von rechts nach links bestimmt.
# Jede Zahl, die kleiner als ihr Vorgänger ist, ist ein neuer Start eines Tupels
n1 = Numbers([1, 2, 3, 4, 5, 6])
self.assertEqual(repr(n1), "[1, 2, 3, 4, 5, 6]")
self.assertEqual(n1.getNumTuples(), 6)
self.assertEqual(n1.getTuples(1), [6])
self.assertEqual(n1.getTuples(2), [5, 6])
self.assertEqual(n1.getTuples(3), [4, 5, 6])
# das ist ein Tupel
n2 = Numbers([3, 3, 3])
self.assertEqual(n2.getNumTuples(), 1)
self.assertEqual(n2.getTuples(1), [3, 3, 3])
n3 = Numbers([3, 2, 1]) # nur ein Tupel drin
self.assertEqual(n3.getNumTuples(), 1)
self.assertEqual(n3.getTuples(1), [3, 2, 1])
n4 = Numbers([1, 10, 1, 10])
self.assertEqual(n4.getNumTuples(), 2)
self.assertEqual(n4.getTuples(1), [10])
self.assertEqual(n4.getTuples(2), [1, 10, 1, 10])
n5 = Numbers([30, 2, 1, 10, 5, 4, 16])
self.assertEqual(n5.getNumTuples(), 3)
self.assertEqual(n5.getTuples(1), [16])
self.assertEqual(n5.getTuples(2), [10, 5, 4, 16])
self.assertEqual(n5.getTuples(3), [30, 2, 1, 10, 5, 4, 16])
def test_roundabout(self):
k1 = Roundabout()
k1.addList([1, 2, 3])
@@ -150,25 +301,22 @@ class TestObjectMethods(unittest.TestCase):
self.assertEqual(k1.remove(), 1)
self.assertEqual(k1.content(), [3, 2])
k2 = Roundabout() # links drehend
k2 = Roundabout(LEFT) # links drehend - default
k2.add(1)
k2.add(2)
k2.add(3)
k2.add(4)
self.assertEqual(k2.content(), [4, 3, 2, 1])
k3 = Roundabout(1) # rechts drehend
k3 = Roundabout(RIGHT) # rechts drehend
k3.add(5)
k3.add(6)
k3.add(7)
k3.add(8)
self.assertEqual(k3.content(), [5, 6, 7, 8])
self.assertEqual(k2.itemOnSeparator(), 1)
self.assertEqual(k3.itemOnSeparator(), 5)
self.assertEqual(k3.remove(), 5)
self.assertEqual(k2.remove(), 1)
@@ -179,6 +327,39 @@ class TestObjectMethods(unittest.TestCase):
self.assertEqual(k5.isEmpty(), False)
self.assertEqual(k5.getSmallestItem(), 1)
def test_roundabout_tuples(self):
k1 = Roundabout(LEFT)
self.assertEqual(k1.isEmpty(), True)
k1.addList([1, 2, 3, 4])
# in a left rotationg roundabout: one tuple is read as ascending from right to left
self.assertEqual(k1.content(), [4, 3, 2, 1])
# this is one tuple
self.assertEqual(k1.getNumTuples(), 1)
k1.clear()
k1.addList([4, 3, 2, 1])
# these are 4 tuples:
self.assertEqual(k1.content(), [1, 2, 3, 4])
self.assertEqual(k1.getNumTuples(), 4)
self.assertEqual(k1.getTuples(2), [3, 4])
self.assertEqual(k1.getTuples(3), [2, 3, 4])
k2 = Roundabout(RIGHT)
self.assertEqual(k2.isEmpty(), True)
k2.addList([1, 2, 3, 4, 5, 6])
# in a right rotationg roundabout: read tuples from left to right
self.assertEqual(k2.content(), [1, 2, 3, 4, 5, 6])
self.assertEqual(k2.getNumTuples(), 1)
k2.clear()
k2.addList([6, 5, 4, 3, 2, 1])
self.assertEqual(k2.getNumTuples(), 6)
self.assertEqual(k2.getTuples(3), [4, 5, 6])
self.assertEqual(k2.getTuples(5), [2, 3, 4, 5, 6])
def test_switch(self):
k1 = Roundabout()
@@ -192,50 +373,124 @@ class TestObjectMethods(unittest.TestCase):
def test_pair(self):
lK = Roundabout()
lK.addList([6, 5, 4, 3, 2, 1])
rK = Roundabout(1)
rK = Roundabout(RIGHT)
sw = Switch(lK, rK)
p = RoundaboutPair(lK, rK, sw)
self.assertEqual(lK.content(), [1, 2, 3, 4, 5, 6])
self.assertEqual(p.show(), ([1, 2, 3, 4, 5, 6], []))
p.sortStep()
p.sortStepPickSort()
self.assertEqual(p.show(), ([6, 1, 2, 3, 4, 5], []))
p.sortStep()
p.sortStepPickSort()
self.assertEqual(p.show(), ([5, 6, 1, 2, 3, 4], []))
p.sortStep()
p.sortStepPickSort()
self.assertEqual(p.show(), ([4, 5, 6, 1, 2, 3], []))
p.sortStep()
p.sortStepPickSort()
self.assertEqual(p.show(), ([3, 4, 5, 6, 1, 2], []))
p.sortStep()
p.sortStepPickSort()
self.assertEqual(p.show(), ([2, 3, 4, 5, 6, 1], []))
p.sortStep()
p.sortStepPickSort()
self.assertEqual(p.show(), ([2, 3, 4, 5, 6], [1]))
def test_picksort(self):
lK = Roundabout()
lK.addList([6, 5, 4, 3, 2, 1])
rK = Roundabout(1)
rK = Roundabout(RIGHT)
sw = Switch(lK, rK)
p = RoundaboutPair(lK, rK, sw)
self.assertEqual(lK.content(), [1, 2, 3, 4, 5, 6])
p.runSimulation()
p.runPickSort()
self.assertEqual(p.show(), ([], [1, 2, 3, 4, 5, 6]))
def test_tuplesort(self):
def test_tuplesort_equal(self):
lK = Roundabout()
lK.addList([3, 2, 1])
rK = Roundabout(1)
rK = Roundabout(RIGHT)
rK.addList([6, 5, 4])
sw = Switch(lK, rK)
p = RoundaboutPair(lK, rK, sw)
sw.set_to_right()
self.assertEqual(p.show(), ([1, 2, 3], [6, 5, 4])) # -> 3, 6
p.sortStepTupleSort()
self.assertEqual(p.show(), ([1, 2], [5, 4, 3, 6])) # -> 2, 5
p.sortStepTupleSort()
self.assertEqual(p.show(), ([5, 2, 1], [4, 3, 6])) # -> 1, 4
p.sortStepTupleSort()
self.assertEqual(p.show(), ([5, 2], [3, 6, 1, 4])) # -> 2, 3, 5, 6
p.sortStepTupleSort()
self.assertEqual(p.show(), ([6, 5, 3, 2], [1, 4])) # -> 1, 2, 3, 4, 5, 6
p.sortStepTupleSort()
self.assertEqual(p.show(), ([], [1, 2, 3, 4, 5, 6]))
def test_tuplesort_stepbystep(self):
lK = Roundabout()
lK.addList([6, 5, 4, 3, 2, 1])
rK = Roundabout(RIGHT)
rK.addList([])
sw = Switch(lK, rK)
p = RoundaboutPair(lK, rK, sw)
sw.set_to_right()
self.assertEqual(p.show(), ([1, 2, 3, 4, 5, 6], []))
# p.sortStepTupleSort()
# self.assertEqual(p.show(), ([1, 2], [5, 4, 3, 6]))
# p.sortStepTupleSort()
# self.assertEqual(p.show(), ([5, 2, 1], [4, 3, 6]))
# p.sortStepTupleSort()
# self.assertEqual(p.show(), ([5, 2], [3, 6, 1, 4]))
# p.sortStepTupleSort()
# self.assertEqual(p.show(), ([6, 5, 3, 2], [1, 4]))
# p.sortStepTupleSort()
# self.assertEqual(p.show(), ([], [1, 2, 3, 4, 5, 6]))
def test_tuplesort_onlyleft(self):
# Roundabouts befüllen
lK = Roundabout()
lK.addList([1, 2, 3, 4, 5])
rK = Roundabout(RIGHT)
rK.addList([])
sw = Switch(lK, rK)
p = RoundaboutPair(lK, rK, sw)
self.assertEqual(p.show(), ([5, 4, 3, 2, 1], [] ))
sw.set_to_right()
# Sortierung starten
p.sortStepTupleSort()
self.assertEqual(p.show(), ([], [1, 2, 3, 4, 5] ))
def test_tuplesort_presorted(self):
lK = Roundabout()
lK.addList([2, 5])
rK = Roundabout(RIGHT)
rK.addList([3, 6, 1, 4])
sw = Switch(lK, rK)
p = RoundaboutPair(lK, rK, sw)
self.assertEqual(p.show(), ([5, 2], [3, 6, 1, 4] ))
sw.set_to_left()
p.sortStepTupleSort()
self.assertEqual(p.show(), ([6, 5, 3, 2], [1, 4] )) # r
p.sortStepTupleSort()
self.assertEqual(p.show(), ([], [1, 2, 3, 4, 5, 6] )) # l
def test_tuplesort_init(self):
lK = Roundabout()
lK.addList([6, 5, 4, 3, 2, 1])
rK = Roundabout(RIGHT)
rK.addList([])
sw = Switch(lK, rK)
p = RoundaboutPair(lK, rK, sw)
sw.set_to_right()
self.assertEqual(p.show(), ([1, 2, 3, 4, 5, 6], []))
p.initTupleSort()
self.assertEqual(p.show(), ([1, 2, 3], [6, 5, 4]))
p.setSortType('t')
p.sortStep()
self.assertEqual(p.show(), ([1, 2], [5, 4, 3, 6]))
if __name__ == '__main__':
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB