added some recursive datastructures
This commit is contained in:
87
recursion/recursive_datastructure/README.md
Normal file
87
recursion/recursive_datastructure/README.md
Normal 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)
|
||||||
|
```
|
56
recursion/recursive_datastructure/lists.py
Normal file
56
recursion/recursive_datastructure/lists.py
Normal 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
|
62
recursion/recursive_datastructure/solution/lists.py
Normal file
62
recursion/recursive_datastructure/solution/lists.py
Normal 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
|
45
recursion/recursive_datastructure/solution/search_trees.py
Normal file
45
recursion/recursive_datastructure/solution/search_trees.py
Normal 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)
|
77
recursion/recursive_datastructure/solution/test_lists.py
Normal file
77
recursion/recursive_datastructure/solution/test_lists.py
Normal 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
|
@ -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))
|
13
recursion/recursive_datastructure/solution/test_trees.py
Normal file
13
recursion/recursive_datastructure/solution/test_trees.py
Normal 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"
|
33
recursion/recursive_datastructure/solution/trees.py
Normal file
33
recursion/recursive_datastructure/solution/trees.py
Normal 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
|
19
recursion/recursive_datastructure/trees.py
Normal file
19
recursion/recursive_datastructure/trees.py
Normal 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
|
Reference in New Issue
Block a user