pandusha 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pandusha/__init__.py ADDED
@@ -0,0 +1,60 @@
1
+ """Pandusha - educational data structures and algorithms library."""
2
+
3
+ from .avl_tree import AVLTree
4
+ from .binary_search_tree import BinarySearchTree
5
+ from .disjoint_set import DisjointSet
6
+ from .doubly_linked_list import DoublyLinkedList
7
+ from .dynamic_programming import (
8
+ climbing_stairs,
9
+ coin_change_min,
10
+ fibonacci_memo,
11
+ fibonacci_tabulation,
12
+ )
13
+ from .graph import Graph
14
+ from .hash_table import HashTable
15
+ from .heap import MaxHeap, MinHeap, PriorityQueue
16
+ from .linked_list import LinkedList
17
+ from .queue import Queue
18
+ from .searching import binary_search, jump_search, linear_search
19
+ from .sorting import (
20
+ bubble_sort,
21
+ heap_sort,
22
+ insertion_sort,
23
+ merge_sort,
24
+ quick_sort,
25
+ selection_sort,
26
+ )
27
+ from .stack import Stack
28
+ from .trie import Trie
29
+
30
+ __version__ = "0.2.0"
31
+ __author__ = "Cem Berk Çakır, Yasemin Serdengeçti"
32
+
33
+ __all__ = [
34
+ "AVLTree",
35
+ "BinarySearchTree",
36
+ "DisjointSet",
37
+ "DoublyLinkedList",
38
+ "Graph",
39
+ "HashTable",
40
+ "LinkedList",
41
+ "MaxHeap",
42
+ "MinHeap",
43
+ "PriorityQueue",
44
+ "Queue",
45
+ "Stack",
46
+ "Trie",
47
+ "binary_search",
48
+ "bubble_sort",
49
+ "climbing_stairs",
50
+ "coin_change_min",
51
+ "fibonacci_memo",
52
+ "fibonacci_tabulation",
53
+ "heap_sort",
54
+ "insertion_sort",
55
+ "jump_search",
56
+ "linear_search",
57
+ "merge_sort",
58
+ "quick_sort",
59
+ "selection_sort",
60
+ ]
pandusha/avl_tree.py ADDED
@@ -0,0 +1,111 @@
1
+ """AVL tree implementation."""
2
+
3
+
4
+ class AVLNode:
5
+ def __init__(self, value):
6
+ self.value = value
7
+ self.left = None
8
+ self.right = None
9
+ self.height = 1
10
+
11
+
12
+ class AVLTree:
13
+ def __init__(self):
14
+ self.root = None
15
+ self.length = 0
16
+
17
+ def __iter__(self):
18
+ return iter(self.in_order())
19
+
20
+ def __len__(self):
21
+ return self.length
22
+
23
+ def __repr__(self):
24
+ return f"AVLTree({self.in_order()!r})"
25
+
26
+ def is_empty(self):
27
+ return self.root is None
28
+
29
+ def insert(self, value):
30
+ if self.contains(value):
31
+ return False
32
+ self.root = self._insert(self.root, value)
33
+ self.length += 1
34
+ return True
35
+
36
+ def contains(self, value):
37
+ current = self.root
38
+ while current is not None:
39
+ if value == current.value:
40
+ return True
41
+ current = current.left if value < current.value else current.right
42
+ return False
43
+
44
+ def in_order(self):
45
+ values = []
46
+
47
+ def walk(node):
48
+ if node is None:
49
+ return
50
+ walk(node.left)
51
+ values.append(node.value)
52
+ walk(node.right)
53
+
54
+ walk(self.root)
55
+ return values
56
+
57
+ def to_list(self):
58
+ return self.in_order()
59
+
60
+ def height(self):
61
+ return self._height(self.root)
62
+
63
+ def _insert(self, node, value):
64
+ if node is None:
65
+ return AVLNode(value)
66
+ if value < node.value:
67
+ node.left = self._insert(node.left, value)
68
+ else:
69
+ node.right = self._insert(node.right, value)
70
+ return self._rebalance(node)
71
+
72
+ def _height(self, node):
73
+ return node.height if node is not None else 0
74
+
75
+ def _balance(self, node):
76
+ return self._height(node.left) - self._height(node.right) if node is not None else 0
77
+
78
+ def _update_height(self, node):
79
+ node.height = 1 + max(self._height(node.left), self._height(node.right))
80
+
81
+ def _rebalance(self, node):
82
+ self._update_height(node)
83
+ balance = self._balance(node)
84
+
85
+ if balance > 1:
86
+ if self._balance(node.left) < 0:
87
+ node.left = self._rotate_left(node.left)
88
+ return self._rotate_right(node)
89
+ if balance < -1:
90
+ if self._balance(node.right) > 0:
91
+ node.right = self._rotate_right(node.right)
92
+ return self._rotate_left(node)
93
+ return node
94
+
95
+ def _rotate_left(self, node):
96
+ new_root = node.right
97
+ moved_subtree = new_root.left
98
+ new_root.left = node
99
+ node.right = moved_subtree
100
+ self._update_height(node)
101
+ self._update_height(new_root)
102
+ return new_root
103
+
104
+ def _rotate_right(self, node):
105
+ new_root = node.left
106
+ moved_subtree = new_root.right
107
+ new_root.right = node
108
+ node.left = moved_subtree
109
+ self._update_height(node)
110
+ self._update_height(new_root)
111
+ return new_root
@@ -0,0 +1,140 @@
1
+ """Binary search tree implementation."""
2
+
3
+ from collections import deque
4
+
5
+
6
+ class Node:
7
+ def __init__(self, value):
8
+ self.value = value
9
+ self.left = None
10
+ self.right = None
11
+
12
+
13
+ class BinarySearchTree:
14
+ def __init__(self):
15
+ self.root = None
16
+ self.length = 0
17
+
18
+ def __iter__(self):
19
+ return iter(self.in_order())
20
+
21
+ def __len__(self):
22
+ return self.length
23
+
24
+ def __repr__(self):
25
+ return f"BinarySearchTree({self.to_list()!r})"
26
+
27
+ def is_empty(self):
28
+ return self.root is None
29
+
30
+ def to_list(self):
31
+ return self.in_order()
32
+
33
+ def insert(self, value):
34
+ new_node = Node(value)
35
+ if self.root is None:
36
+ self.root = new_node
37
+ self.length += 1
38
+ return True
39
+
40
+ current = self.root
41
+ while True:
42
+ if value == current.value:
43
+ return False
44
+ if value < current.value:
45
+ if current.left is None:
46
+ current.left = new_node
47
+ self.length += 1
48
+ return True
49
+ current = current.left
50
+ else:
51
+ if current.right is None:
52
+ current.right = new_node
53
+ self.length += 1
54
+ return True
55
+ current = current.right
56
+
57
+ def contains(self, value):
58
+ current = self.root
59
+ while current is not None:
60
+ if value == current.value:
61
+ return True
62
+ current = current.left if value < current.value else current.right
63
+ return False
64
+
65
+ def in_order(self):
66
+ values = []
67
+
68
+ def walk(node):
69
+ if node is None:
70
+ return
71
+ walk(node.left)
72
+ values.append(node.value)
73
+ walk(node.right)
74
+
75
+ walk(self.root)
76
+ return values
77
+
78
+ def pre_order(self):
79
+ values = []
80
+
81
+ def walk(node):
82
+ if node is None:
83
+ return
84
+ values.append(node.value)
85
+ walk(node.left)
86
+ walk(node.right)
87
+
88
+ walk(self.root)
89
+ return values
90
+
91
+ def post_order(self):
92
+ values = []
93
+
94
+ def walk(node):
95
+ if node is None:
96
+ return
97
+ walk(node.left)
98
+ walk(node.right)
99
+ values.append(node.value)
100
+
101
+ walk(self.root)
102
+ return values
103
+
104
+ def level_order(self):
105
+ if self.root is None:
106
+ return []
107
+ values = []
108
+ queue = deque([self.root])
109
+ while queue:
110
+ node = queue.popleft()
111
+ values.append(node.value)
112
+ if node.left is not None:
113
+ queue.append(node.left)
114
+ if node.right is not None:
115
+ queue.append(node.right)
116
+ return values
117
+
118
+ def min_value(self):
119
+ current = self.root
120
+ if current is None:
121
+ return None
122
+ while current.left is not None:
123
+ current = current.left
124
+ return current.value
125
+
126
+ def max_value(self):
127
+ current = self.root
128
+ if current is None:
129
+ return None
130
+ while current.right is not None:
131
+ current = current.right
132
+ return current.value
133
+
134
+ def height(self):
135
+ def walk(node):
136
+ if node is None:
137
+ return 0
138
+ return 1 + max(walk(node.left), walk(node.right))
139
+
140
+ return walk(self.root)
@@ -0,0 +1,44 @@
1
+ """Disjoint set / union-find implementation."""
2
+
3
+
4
+ class DisjointSet:
5
+ def __init__(self):
6
+ self.parent = {}
7
+ self.rank = {}
8
+
9
+ def __len__(self):
10
+ return len(self.parent)
11
+
12
+ def make_set(self, item):
13
+ if item in self.parent:
14
+ return False
15
+ self.parent[item] = item
16
+ self.rank[item] = 0
17
+ return True
18
+
19
+ def find(self, item):
20
+ if item not in self.parent:
21
+ self.make_set(item)
22
+ if self.parent[item] != item:
23
+ self.parent[item] = self.find(self.parent[item])
24
+ return self.parent[item]
25
+
26
+ def union(self, first, second):
27
+ root_first = self.find(first)
28
+ root_second = self.find(second)
29
+ if root_first == root_second:
30
+ return False
31
+
32
+ if self.rank[root_first] < self.rank[root_second]:
33
+ self.parent[root_first] = root_second
34
+ elif self.rank[root_first] > self.rank[root_second]:
35
+ self.parent[root_second] = root_first
36
+ else:
37
+ self.parent[root_second] = root_first
38
+ self.rank[root_first] += 1
39
+ return True
40
+
41
+ def connected(self, first, second):
42
+ if first not in self.parent or second not in self.parent:
43
+ return False
44
+ return self.find(first) == self.find(second)
@@ -0,0 +1,232 @@
1
+ """Doubly linked list implementation."""
2
+
3
+
4
+ _MISSING = object()
5
+
6
+
7
+ class DoublyLinkedList:
8
+ class Node:
9
+ def __init__(self, value):
10
+ self.value = value
11
+ self.next = None
12
+ self.prev = None
13
+
14
+ def __repr__(self):
15
+ return f"Node({self.value!r})"
16
+
17
+ def __init__(self, value=_MISSING):
18
+ self.head = None
19
+ self.tail = None
20
+ self.length = 0
21
+ if value is not _MISSING:
22
+ self.append(value)
23
+
24
+ def __iter__(self):
25
+ current = self.head
26
+ while current is not None:
27
+ yield current.value
28
+ current = current.next
29
+
30
+ def __len__(self):
31
+ return self.length
32
+
33
+ def __repr__(self):
34
+ return f"DoublyLinkedList({self.to_list()!r})"
35
+
36
+ def get_head(self):
37
+ return self.head
38
+
39
+ def get_tail(self):
40
+ return self.tail
41
+
42
+ def get_length(self):
43
+ return self.length
44
+
45
+ def is_empty(self):
46
+ return self.length == 0
47
+
48
+ def to_list(self):
49
+ return list(self)
50
+
51
+ def print_list(self):
52
+ current = self.head
53
+ while current is not None:
54
+ print(current.value)
55
+ current = current.next
56
+
57
+ def print_all(self):
58
+ print("Head:", self.head.value if self.head else "Null")
59
+ print("Tail:", self.tail.value if self.tail else "Null")
60
+ print("Length:", self.length)
61
+ print("\nDoubly Linked List:")
62
+ if self.is_empty():
63
+ print("Empty")
64
+ else:
65
+ self.print_list()
66
+
67
+ def make_empty(self):
68
+ self.head = None
69
+ self.tail = None
70
+ self.length = 0
71
+
72
+ def clear(self):
73
+ self.make_empty()
74
+
75
+ def append(self, value):
76
+ new_node = self.Node(value)
77
+ if self.is_empty():
78
+ self.head = new_node
79
+ self.tail = new_node
80
+ else:
81
+ self.tail.next = new_node
82
+ new_node.prev = self.tail
83
+ self.tail = new_node
84
+ self.length += 1
85
+ return True
86
+
87
+ def remove_last(self):
88
+ if self.is_empty():
89
+ return None
90
+ temp = self.tail
91
+ if self.length == 1:
92
+ self.make_empty()
93
+ return temp
94
+ else:
95
+ self.tail = self.tail.prev
96
+ self.tail.next = None
97
+ temp.prev = None
98
+ self.length -= 1
99
+ return temp
100
+
101
+ def prepend(self, value):
102
+ new_node = self.Node(value)
103
+ if self.is_empty():
104
+ self.head = new_node
105
+ self.tail = new_node
106
+ else:
107
+ new_node.next = self.head
108
+ self.head.prev = new_node
109
+ self.head = new_node
110
+ self.length += 1
111
+ return True
112
+
113
+ def remove_first(self):
114
+ if self.is_empty():
115
+ return None
116
+ temp = self.head
117
+ if self.length == 1:
118
+ self.make_empty()
119
+ else:
120
+ self.head = self.head.next
121
+ self.head.prev = None
122
+ temp.next = None
123
+ self.length -= 1
124
+ return temp
125
+
126
+ def get(self, index):
127
+ if index < 0 or index >= self.length:
128
+ return None
129
+ if index < self.length / 2:
130
+ current = self.head
131
+ for _ in range(index):
132
+ current = current.next
133
+ else:
134
+ current = self.tail
135
+ for _ in range(self.length - 1, index, -1):
136
+ current = current.prev
137
+ return current
138
+
139
+ def set(self, index, value):
140
+ temp = self.get(index)
141
+ if temp is None:
142
+ return False
143
+ temp.value = value
144
+ return True
145
+
146
+ def set_value(self, index, value):
147
+ return self.set(index, value)
148
+
149
+ def insert(self, index, value):
150
+ if index < 0 or index > self.length:
151
+ return False
152
+ if index == 0:
153
+ return self.prepend(value)
154
+ if index == self.length:
155
+ return self.append(value)
156
+
157
+ new_node = self.Node(value)
158
+ before = self.get(index - 1)
159
+ after = before.next
160
+ new_node.prev = before
161
+ new_node.next = after
162
+ before.next = new_node
163
+ after.prev = new_node
164
+ self.length += 1
165
+ return True
166
+
167
+ def remove(self, index):
168
+ if index < 0 or index >= self.length:
169
+ return None
170
+ if index == 0:
171
+ return self.remove_first()
172
+ if index == self.length - 1:
173
+ return self.remove_last()
174
+
175
+ temp = self.get(index)
176
+ temp.prev.next = temp.next
177
+ temp.next.prev = temp.prev
178
+ temp.prev = None
179
+ temp.next = None
180
+ self.length -= 1
181
+ return temp
182
+
183
+ def is_palindrome(self):
184
+ left = self.head
185
+ right = self.tail
186
+ while left is not None and right is not None and left != right and left.prev != right:
187
+ if left.value != right.value:
188
+ return False
189
+ left = left.next
190
+ right = right.prev
191
+ return True
192
+
193
+ def reverse(self):
194
+ current = self.head
195
+ self.head, self.tail = self.tail, self.head
196
+ while current is not None:
197
+ current.prev, current.next = current.next, current.prev
198
+ current = current.prev
199
+
200
+ def partition_list(self, x):
201
+ less = []
202
+ greater_or_equal = []
203
+ current = self.head
204
+ while current is not None:
205
+ if current.value < x:
206
+ less.append(current.value)
207
+ else:
208
+ greater_or_equal.append(current.value)
209
+ current = current.next
210
+ self.make_empty()
211
+ for value in less + greater_or_equal:
212
+ self.append(value)
213
+
214
+ def reverse_between(self, start, end):
215
+ if self.head is None or start == end:
216
+ return
217
+ if start < 0 or end < start or end >= self.length:
218
+ return
219
+
220
+ values = self.to_list()
221
+ values[start : end + 1] = reversed(values[start : end + 1])
222
+ self.make_empty()
223
+ for value in values:
224
+ self.append(value)
225
+
226
+ def swap_pairs(self):
227
+ values = self.to_list()
228
+ for index in range(0, len(values) - 1, 2):
229
+ values[index], values[index + 1] = values[index + 1], values[index]
230
+ self.make_empty()
231
+ for value in values:
232
+ self.append(value)
@@ -0,0 +1,49 @@
1
+ """Small dynamic programming examples."""
2
+
3
+
4
+ def fibonacci_memo(n, memo=None):
5
+ if n < 0:
6
+ raise ValueError("n must be non-negative")
7
+ if memo is None:
8
+ memo = {}
9
+ if n in (0, 1):
10
+ return n
11
+ if n not in memo:
12
+ memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo)
13
+ return memo[n]
14
+
15
+
16
+ def fibonacci_tabulation(n):
17
+ if n < 0:
18
+ raise ValueError("n must be non-negative")
19
+ if n in (0, 1):
20
+ return n
21
+ table = [0] * (n + 1)
22
+ table[1] = 1
23
+ for index in range(2, n + 1):
24
+ table[index] = table[index - 1] + table[index - 2]
25
+ return table[n]
26
+
27
+
28
+ def climbing_stairs(n):
29
+ if n < 0:
30
+ raise ValueError("n must be non-negative")
31
+ if n <= 1:
32
+ return 1
33
+ previous = 1
34
+ current = 1
35
+ for _ in range(2, n + 1):
36
+ previous, current = current, previous + current
37
+ return current
38
+
39
+
40
+ def coin_change_min(coins, amount):
41
+ if amount < 0:
42
+ raise ValueError("amount must be non-negative")
43
+ dp = [float("inf")] * (amount + 1)
44
+ dp[0] = 0
45
+ for value in range(1, amount + 1):
46
+ for coin in coins:
47
+ if coin <= value:
48
+ dp[value] = min(dp[value], dp[value - coin] + 1)
49
+ return dp[amount] if dp[amount] != float("inf") else -1