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