diff --git a/recursion/recursive_datastructure/README.md b/recursion/recursive_datastructure/README.md new file mode 100644 index 0000000..ea692a1 --- /dev/null +++ b/recursion/recursive_datastructure/README.md @@ -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) +``` \ No newline at end of file diff --git a/recursion/recursive_datastructure/lists.py b/recursion/recursive_datastructure/lists.py new file mode 100644 index 0000000..6794837 --- /dev/null +++ b/recursion/recursive_datastructure/lists.py @@ -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 \ No newline at end of file diff --git a/recursion/recursive_datastructure/solution/lists.py b/recursion/recursive_datastructure/solution/lists.py new file mode 100644 index 0000000..bd24ae9 --- /dev/null +++ b/recursion/recursive_datastructure/solution/lists.py @@ -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 \ No newline at end of file diff --git a/recursion/recursive_datastructure/solution/search_trees.py b/recursion/recursive_datastructure/solution/search_trees.py new file mode 100644 index 0000000..847014e --- /dev/null +++ b/recursion/recursive_datastructure/solution/search_trees.py @@ -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) \ No newline at end of file diff --git a/recursion/recursive_datastructure/solution/test_lists.py b/recursion/recursive_datastructure/solution/test_lists.py new file mode 100644 index 0000000..23c0823 --- /dev/null +++ b/recursion/recursive_datastructure/solution/test_lists.py @@ -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 \ No newline at end of file diff --git a/recursion/recursive_datastructure/solution/test_search_trees.py b/recursion/recursive_datastructure/solution/test_search_trees.py new file mode 100644 index 0000000..7b70672 --- /dev/null +++ b/recursion/recursive_datastructure/solution/test_search_trees.py @@ -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)) \ No newline at end of file diff --git a/recursion/recursive_datastructure/solution/test_trees.py b/recursion/recursive_datastructure/solution/test_trees.py new file mode 100644 index 0000000..4534c4e --- /dev/null +++ b/recursion/recursive_datastructure/solution/test_trees.py @@ -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" \ No newline at end of file diff --git a/recursion/recursive_datastructure/solution/trees.py b/recursion/recursive_datastructure/solution/trees.py new file mode 100644 index 0000000..e737a87 --- /dev/null +++ b/recursion/recursive_datastructure/solution/trees.py @@ -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 \ No newline at end of file diff --git a/recursion/recursive_datastructure/trees.py b/recursion/recursive_datastructure/trees.py new file mode 100644 index 0000000..b9cc1d7 --- /dev/null +++ b/recursion/recursive_datastructure/trees.py @@ -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 \ No newline at end of file