added some recursive datastructures

This commit is contained in:
2024-02-14 18:44:59 +01:00
parent 36b6a78833
commit 4c5d27e8d1
9 changed files with 416 additions and 0 deletions

View File

@ -0,0 +1,87 @@
# Rekursive Datenstrukturen
## [LinkedList](./lists.py)
In der Vorlesung wurden uns rekursive Datenstrukturen vorgestellt. Und zwar in der einfachsten Form, einer Liste. Hierbei modellieren wir eine *Node* Datenstruktur die unseren Wert *Value* trägt und auf die nächste *Node* in der Liste verweist durch *next_node*.
```python
from dataclasses import dataclass
from typing import Optional
@dataclass
class Node[T]:
value: T
next_node: Optional['Node[T]']
```
Jetzt wollen wir eine Liste um unsere Node modellieren, hierbei interessiert uns nur die erste Node *__head* und die Länge *__length*.
```python
@dataclass
class LinkedList[T]:
def __post_init__(self):
self.__head: Optional[Node[T]] = None
self.__length: int = 0
```
Jetzt geht es darum alle Standardmethoden die eine Liste braucht zu implementieren.
## [BinaryTree](./trees.py)
Jetzt wollen wir eine andere rekursive Datenstruktur definieren, den binary Tree. Diese besteht immer aus zwei Abzweigungen *right* und *left*.
```python
@dataclass
class Node[T]:
value: T
left: Optional['Node[T]']
right: Optional['Node[T]']
```
Statt jetzt eine eigene Klasse zu erstellen die `Node` intern benutzt wollen wir einfach einen Typalias erstellen und imperative mit Funktionen statt Methoden arbeiten.
```python
type BinaryTree[T] = Optional[Node[T]]
```
### `traverse`
Man kann einen BinaryTree auf drei typische arten ablaufen
- Inorder
- Erst laufen wir Links ab
- Dann schauen wir uns die Node an
- Dann laufen wir Rechts ab
- Preorder
- Erst schauen wir uns die Node an
- Dann laufen wir Links ab
- Dann laufen wir Rechts ab
- Postorder
- Erst laufen wir Links ab
- Dann laufen wir Rechts ab
- Dann schauen wir uns die Node an
Hierfür erstellen wir einen `Enum` um zu unterscheiden wie wir gerade ablaufen
```python
class TraversalType(Enum):
INORDER, POSTORDER, PREORDER = auto(), auto(), auto()
```
Und jetzt implementieren wir einen Generator der uns einen Iterator erzeugt welcher den BinaryTree in der gegebenen Order abläuft.
```python
def traverse[T](tree: BinaryTree[T], order: TraversalType = TraversalType.INORDER) -> Iterator[T]:
pass
```
Tipp:
Mit `yield from ...` kann man einen ganzen Iterator *yield*en.
```python
def my_range(start: int, end: int) -> Iterator[int]:
yield from range(start, end)
```

View File

@ -0,0 +1,56 @@
from dataclasses import dataclass
from typing import Iterator, Optional
@dataclass
class Node[T]:
value: T
next_node: Optional['Node[T]']
@dataclass
class LinkedList[T]:
def __post_init__(self):
# unser erstes Element
self.__head: Optional[Node[T]] = None
# Die Länge der Liste
self.__length = 0
def __eq__(self, other: 'LinkedList[T]') -> bool:
"""Hier wollen wir alle Elemente von dieser Liste mit allen Elementen von einer anderen Liste vergleichen.
Hierbei kann man von ausgehen dass T vergleichbar ist.
Das Programm sollte nicht abstürzen bei unterschiedlichen längen etc. sondern einfach False zurückgeben"""
pass
def __iter__(self) -> Iterator[T]:
"""Hier wollen wir einen Iterator über alle Elemente haben, der Iterator kann leer sein
Tipp: Generator"""
pass
def append(self, value: T) -> None:
"""Hier wollen wir einfach `value` an das Ende der Liste setzen und die Länge erhöhen"""
pass
def remove(self, value: T):
"""Hier wollen wir den Wert `value` aus unserer Liste entfernen, wir können davon ausgehen dass T vergleichbar ist.
Wenn wir `value` nicht finden soll einfach nichts passieren"""
pass
def __len__(self) -> int:
"""Hier soll die Länge der Liste zurückgegeben werden"""
pass
def __getitem__(self, index: int) -> T:
"""_summary_
__getitem__ definiert das verhalten vom [] index operator wobei `index` der Wert ist der in [] übergeben wird
Hierbei sollen wir das Item `T` an Stelle `index` zurückgeben und sonst einen IndexError raisen
Args:
index (int): der Index von dem Item was wir suchen
Returns:
T: das Item an stelle `index`
"""
pass

View File

@ -0,0 +1,62 @@
from dataclasses import dataclass
from typing import Iterator, Optional
@dataclass
class Node[T]:
value: T
next_node: Optional['Node[T]']
@dataclass
class LinkedList[T]:
def __post_init__(self):
self.__head: Optional[Node[T]] = None
self.__length = 0
def __eq__(self, other: 'LinkedList[T]') -> bool:
if len(self) != len(other):
return False
for i in range(0, len(self)):
if self[i] != self[i]:
return False
return True
def __iter__(self) -> Iterator[T]:
node = self.__head
while node:
yield node.value
node = node.next_node
def append(self, value: T):
if not self.__head:
self.__head = Node(value, None)
else:
node = self.__head
while node.next_node:
node = node.next_node
node.next_node = Node(value, None)
self.__length += 1
def remove(self, value: T):
if self.__head and self.__head.value == value:
self.__head = self.__head.next_node
self.__length -= 1
elif self.__head:
node = self.__head
while node.next_node and node.next_node.value != value:
node = node.next_node
if node.next_node and node.next_node.value == value:
node.next_node = node.next_node.next_node
self.__length -= 1
def __len__(self) -> int:
return self.__length
def __getitem__(self, index: int) -> T:
if index >= len(self):
raise IndexError
node = self.__head
for _ in range(0, index):
node = node.next_node
return node.value

View File

@ -0,0 +1,45 @@
from typing import Iterator, Optional
from trees import Node, traverse
from dataclasses import InitVar, dataclass
@dataclass
class BinarySearchTree[T]:
def __post_init__(self):
self.__root: Optional[Node[T]] = None
def insert(self, value: T):
prev = None
curr = self.__root
while curr:
prev = curr
if curr.value > value:
curr = curr.left
else:
curr = curr.right
if prev is None:
self.__root = Node(value, None, None)
elif value < prev.value:
prev.left = Node(value, None, None)
else:
prev.right = Node(value, None, None)
def __delete(prev: Node[T], to_delete: Node[T]) -> Node[T]:
# TODO: implement
pass
def remove(self, value: T):
prev = None
curr = self.__root
while curr:
prev = curr
if curr.value > value:
curr = curr.left
elif curr.value < value:
curr = curr.right
else:
curr = self.__delete(prev, curr)
def __iter__(self) -> Iterator[T]:
yield from traverse(self.__root)

View File

@ -0,0 +1,77 @@
from lists import LinkedList
def test_append():
lst = LinkedList[int]()
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
lst2 = LinkedList[int]()
lst2.append(1)
lst2.append(2)
lst2.append(3)
lst2.append(4)
assert lst == lst2
def test_remove():
lst = LinkedList[int]()
lst.remove(0)
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
lst.remove(2)
lst.remove(5)
lst2 = LinkedList[int]()
lst2.append(1)
lst2.append(3)
lst2.append(4)
assert lst == lst2
def test_length():
lst = LinkedList[int]()
lst.append(0)
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
assert len(lst) == 5
lst.remove(0)
assert len(lst) == 4
lst.remove(2)
assert len(lst) == 3
lst.remove(1)
assert len(lst) == 2
lst.remove(3)
assert len(lst) == 1
lst.remove(4)
assert len(lst) == 0
lst.remove(4)
lst.remove(5)
assert len(lst) == 0
def test_index():
lst = LinkedList[int]()
lst.append(0)
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
assert lst[0] == 0
assert lst[1] == 1
assert lst[2] == 2
assert lst[3] == 3
assert lst[4] == 4
def test_iter():
lst = LinkedList[int]()
lst.append(0)
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
j = 0
for i in iter(lst):
assert i == j
j += 1

View File

@ -0,0 +1,24 @@
from search_trees import BinarySearchTree
from random import randint as random
MAX= 1000
MIN = 0
LENGTH = 100
def test_insert():
nums = [random(MIN, MAX) for _ in range(0, LENGTH)]
bst = BinarySearchTree()
for num in nums:
bst.insert(num)
assert list(iter(bst)) == sorted(nums)
def test_remove():
nums = {random(MIN, MAX) for _ in range(0, LENGTH)}
fil = {random(MIN, MAX) for _ in range(0, LENGTH // 4)}
bst = BinarySearchTree()
for num in nums:
bst.insert(num)
for num in fil:
bst.remove(num)
assert list(iter(bst)) == sorted(filter(lambda x: x not in fil, nums))

View File

@ -0,0 +1,13 @@
from trees import Node, traverse, TraversalType
def test_traverse():
tree = Node(1, Node(2, Node(3, None, None), Node(4, Node(
5, None, None), None)), Node(6, Node(7, None, None), Node(8, None, None)))
assert ", ".join(map(lambda x: str(x), traverse(tree))
) == "3, 2, 5, 4, 1, 7, 6, 8"
assert ", ".join(map(lambda x: str(x), traverse(
tree, TraversalType.PREORDER))) == "1, 2, 3, 4, 5, 6, 7, 8"
assert ", ".join(map(lambda x: str(x), traverse(
tree, TraversalType.POSTORDER))) == "3, 5, 4, 2, 7, 8, 6, 1"

View File

@ -0,0 +1,33 @@
from dataclasses import dataclass
from enum import Enum, auto
from typing import Iterator, Optional
@dataclass
class Node[T]:
value: T
left: Optional['Node[T]']
right: Optional['Node[T]']
type BinaryTree[T] = Optional[Node[T]]
class TraversalType(Enum):
INORDER, POSTORDER, PREORDER = auto(), auto(), auto()
def traverse[T](tree: BinaryTree[T], order: TraversalType = TraversalType.INORDER) -> Iterator[T]:
match (tree, order):
case (Node(value, left, right), TraversalType.INORDER):
yield from traverse(left, order)
yield value
yield from traverse(right, order)
case (Node(value, left, right), TraversalType.POSTORDER):
yield from traverse(left, order)
yield from traverse(right, order)
yield value
case (Node(value, left, right), TraversalType.PREORDER):
yield value
yield from traverse(left, order)
yield from traverse(right, order)
case _:
return

View File

@ -0,0 +1,19 @@
from dataclasses import dataclass
from enum import Enum, auto
from typing import Iterator, Optional
@dataclass
class Node[T]:
value: T
left: Optional['Node[T]']
right: Optional['Node[T]']
type BinaryTree[T] = Optional[Node[T]]
class TraversalType(Enum):
INORDER, POSTORDER, PREORDER = auto(), auto(), auto()
def traverse[T](tree: BinaryTree[T], order: TraversalType = TraversalType.INORDER) -> Iterator[T]:
pass