From 68911421edad3b17246b7bda872049038ee4e07c Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 15:51:46 +0800 Subject: [PATCH 001/156] Add doctests for butterfly_pattern.py --- graphics/butterfly_pattern.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/graphics/butterfly_pattern.py b/graphics/butterfly_pattern.py index 7913b03a7e95..c8758dab39ce 100644 --- a/graphics/butterfly_pattern.py +++ b/graphics/butterfly_pattern.py @@ -44,3 +44,8 @@ def butterfly_pattern(n: int) -> str: if __name__ == "__main__": n = int(input("Enter the size of the butterfly pattern: ")) print(butterfly_pattern(n)) + +if __name__ == "__main__": + import doctest + # Run the doctests + doctest.testmod() From 22083ae85e204a609e46ef5d849cd1bcb03ccce8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 07:57:32 +0000 Subject: [PATCH 002/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- graphics/butterfly_pattern.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphics/butterfly_pattern.py b/graphics/butterfly_pattern.py index c8758dab39ce..8eea6c090690 100644 --- a/graphics/butterfly_pattern.py +++ b/graphics/butterfly_pattern.py @@ -47,5 +47,6 @@ def butterfly_pattern(n: int) -> str: if __name__ == "__main__": import doctest + # Run the doctests doctest.testmod() From d9b07dfb1017adaa3994510cf4612c0c27f9947b Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 16:02:18 +0800 Subject: [PATCH 003/156] Update minimum_spanning_tree_kruskal2.py --- graphs/minimum_spanning_tree_kruskal2.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/graphs/minimum_spanning_tree_kruskal2.py b/graphs/minimum_spanning_tree_kruskal2.py index 0ddb43ce8e6e..80681537088b 100644 --- a/graphs/minimum_spanning_tree_kruskal2.py +++ b/graphs/minimum_spanning_tree_kruskal2.py @@ -1,11 +1,6 @@ from __future__ import annotations -from typing import Generic, TypeVar - -T = TypeVar("T") - - -class DisjointSetTreeNode(Generic[T]): +class DisjointSetTreeNode[T]: # Disjoint Set Node to store the parent and rank def __init__(self, data: T) -> None: self.data = data @@ -13,7 +8,7 @@ def __init__(self, data: T) -> None: self.rank = 0 -class DisjointSetTree(Generic[T]): +class DisjointSetTree[T]: # Disjoint Set DataStructure def __init__(self) -> None: # map from node name to the node object @@ -46,7 +41,7 @@ def union(self, data1: T, data2: T) -> None: self.link(self.find_set(data1), self.find_set(data2)) -class GraphUndirectedWeighted(Generic[T]): +class GraphUndirectedWeighted[T]: def __init__(self) -> None: # connections: map from the node to the neighbouring nodes (with weights) self.connections: dict[T, dict[T, int]] = {} @@ -118,4 +113,5 @@ def kruskal(self) -> GraphUndirectedWeighted[T]: num_edges += 1 graph.add_edge(u, v, w) disjoint_set.union(u, v) + # Return the generated Minimum Spanning Tree return graph From 97b33b280785ee41f5ff1ec7ca87bdd57193a416 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 08:02:41 +0000 Subject: [PATCH 004/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- graphs/minimum_spanning_tree_kruskal2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphs/minimum_spanning_tree_kruskal2.py b/graphs/minimum_spanning_tree_kruskal2.py index 80681537088b..36ca1cbb2615 100644 --- a/graphs/minimum_spanning_tree_kruskal2.py +++ b/graphs/minimum_spanning_tree_kruskal2.py @@ -1,5 +1,6 @@ from __future__ import annotations + class DisjointSetTreeNode[T]: # Disjoint Set Node to store the parent and rank def __init__(self, data: T) -> None: From 158128cec798136bb0298c73fd45887b8aea7de1 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 16:09:21 +0800 Subject: [PATCH 005/156] Update lru_cache.py --- other/lru_cache.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/other/lru_cache.py b/other/lru_cache.py index 4f0c843c86cc..31fa4d3b5d28 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -1,13 +1,9 @@ from __future__ import annotations from collections.abc import Callable -from typing import Generic, TypeVar -T = TypeVar("T") -U = TypeVar("U") - -class DoubleLinkedListNode(Generic[T, U]): +class DoubleLinkedListNode[T, U]: """ Double Linked List Node built specifically for LRU Cache @@ -28,7 +24,7 @@ def __repr__(self) -> str: ) -class DoubleLinkedList(Generic[T, U]): +class DoubleLinkedList[T, U]: """ Double Linked List built specifically for LRU Cache @@ -143,7 +139,7 @@ def remove( return node -class LRUCache(Generic[T, U]): +class LRUCache[T, U]: """ LRU Cache to store a given capacity of data. Can be used as a stand-alone object or as a function decorator. @@ -227,8 +223,7 @@ def __repr__(self) -> str: f"CacheInfo(hits={self.hits}, misses={self.miss}, " f"capacity={self.capacity}, current size={self.num_keys})" ) - - def __contains__(self, key: T) -> bool: + def __contains__(self, key: T) -> bool: """ >>> cache = LRUCache(1) From cecabe3a4dc5b3f268ad86452db4426480932eb0 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 16:14:05 +0800 Subject: [PATCH 006/156] Update lru_cache.py --- other/lru_cache.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/other/lru_cache.py b/other/lru_cache.py index 31fa4d3b5d28..315363a1b20f 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -2,7 +2,6 @@ from collections.abc import Callable - class DoubleLinkedListNode[T, U]: """ Double Linked List Node built specifically for LRU Cache @@ -218,12 +217,12 @@ def __repr__(self) -> str: Return the details for the cache instance [hits, misses, capacity, current_size] """ - return ( f"CacheInfo(hits={self.hits}, misses={self.miss}, " f"capacity={self.capacity}, current size={self.num_keys})" ) - def __contains__(self, key: T) -> bool: + + def __contains__(self, key: T) -> bool: """ >>> cache = LRUCache(1) @@ -235,9 +234,7 @@ def __contains__(self, key: T) -> bool: >>> 1 in cache True """ - return key in self.cache - def get(self, key: T) -> U | None: """ Returns the value for the input key and updates the Double Linked List. @@ -262,7 +259,6 @@ def put(self, key: T, value: U) -> None: """ Sets the value for the input key and updates the Double Linked List """ - if key not in self.cache: if self.num_keys >= self.capacity: # delete first node (oldest) when over capacity @@ -281,7 +277,6 @@ def put(self, key: T, value: U) -> None: self.cache[key] = DoubleLinkedListNode(key, value) self.list.add(self.cache[key]) self.num_keys += 1 - else: # bump node to the end of the list, update value node = self.list.remove(self.cache[key]) @@ -314,7 +309,6 @@ def cache_decorator_wrapper(*args: T) -> U: result = func(*args) decorator_function_to_instance_map[func].put(args[0], result) return result - def cache_info() -> LRUCache[T, U]: return decorator_function_to_instance_map[func] From 9d16049c5022dbffc9ca9cc83ae0c5f8a8e4e61d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 08:14:28 +0000 Subject: [PATCH 007/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- other/lru_cache.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/other/lru_cache.py b/other/lru_cache.py index 315363a1b20f..1d9a67f4ad0b 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -2,6 +2,7 @@ from collections.abc import Callable + class DoubleLinkedListNode[T, U]: """ Double Linked List Node built specifically for LRU Cache @@ -235,6 +236,7 @@ def __contains__(self, key: T) -> bool: True """ return key in self.cache + def get(self, key: T) -> U | None: """ Returns the value for the input key and updates the Double Linked List. @@ -309,6 +311,7 @@ def cache_decorator_wrapper(*args: T) -> U: result = func(*args) decorator_function_to_instance_map[func].put(args[0], result) return result + def cache_info() -> LRUCache[T, U]: return decorator_function_to_instance_map[func] From 2c17e86a0dccc5ac0dedc8297ec4e4d9c6037152 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 16:32:31 +0800 Subject: [PATCH 008/156] Update lfu_cache.py --- other/lfu_cache.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/other/lfu_cache.py b/other/lfu_cache.py index 5a143c739b9d..3939fe8610a0 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -7,7 +7,7 @@ U = TypeVar("U") -class DoubleLinkedListNode(Generic[T, U]): +class DoubleLinkedListNode[T, U]: """ Double Linked List Node built specifically for LFU Cache @@ -30,7 +30,7 @@ def __repr__(self) -> str: ) -class DoubleLinkedList(Generic[T, U]): +class DoubleLinkedList[T, U]: """ Double Linked List built specifically for LFU Cache @@ -96,7 +96,6 @@ class DoubleLinkedList(Generic[T, U]): """ - def __init__(self) -> None: self.head: DoubleLinkedListNode[T, U] = DoubleLinkedListNode(None, None) self.rear: DoubleLinkedListNode[T, U] = DoubleLinkedListNode(None, None) @@ -159,9 +158,7 @@ def remove( node.prev = None node.next = None return node - - -class LFUCache(Generic[T, U]): +class LFUCache[T, U]: """ LFU Cache to store a given capacity of data. Can be used as a stand-alone object or as a function decorator. @@ -276,7 +273,6 @@ def put(self, key: T, value: U) -> None: assert node is not None # node guaranteed to be in list node.val = value self.list.add(node) - @classmethod def decorator( cls: type[LFUCache[T, U]], size: int = 128 From fcadabea4b5fba67e568d64ed640a11619bf151f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 08:32:55 +0000 Subject: [PATCH 009/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- other/lfu_cache.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/other/lfu_cache.py b/other/lfu_cache.py index 3939fe8610a0..5add9c42a21a 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -96,6 +96,7 @@ class DoubleLinkedList[T, U]: """ + def __init__(self) -> None: self.head: DoubleLinkedListNode[T, U] = DoubleLinkedListNode(None, None) self.rear: DoubleLinkedListNode[T, U] = DoubleLinkedListNode(None, None) @@ -158,6 +159,8 @@ def remove( node.prev = None node.next = None return node + + class LFUCache[T, U]: """ LFU Cache to store a given capacity of data. Can be used as a stand-alone object @@ -273,6 +276,7 @@ def put(self, key: T, value: U) -> None: assert node is not None # node guaranteed to be in list node.val = value self.list.add(node) + @classmethod def decorator( cls: type[LFUCache[T, U]], size: int = 128 From 1e71b72b0c4d311fb2850ac060eb9bf406e5fac3 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 16:34:02 +0800 Subject: [PATCH 010/156] Update lfu_cache.py --- other/lfu_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/other/lfu_cache.py b/other/lfu_cache.py index 5add9c42a21a..6eaacff2966a 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Callable -from typing import Generic, TypeVar +from typing import TypeVar T = TypeVar("T") U = TypeVar("U") From 34da76f94e723310002f53267af199ce7331f2a7 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 16:45:15 +0800 Subject: [PATCH 011/156] Update stack_with_doubly_linked_list.py --- data_structures/stacks/stack_with_doubly_linked_list.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/data_structures/stacks/stack_with_doubly_linked_list.py b/data_structures/stacks/stack_with_doubly_linked_list.py index 50c5236e073c..8b472293721f 100644 --- a/data_structures/stacks/stack_with_doubly_linked_list.py +++ b/data_structures/stacks/stack_with_doubly_linked_list.py @@ -3,19 +3,15 @@ from __future__ import annotations -from typing import Generic, TypeVar -T = TypeVar("T") - - -class Node(Generic[T]): +class Node[T]: def __init__(self, data: T): self.data = data # Assign data self.next: Node[T] | None = None # Initialize next as null self.prev: Node[T] | None = None # Initialize prev as null -class Stack(Generic[T]): +class Stack[T]: """ >>> stack = Stack() >>> stack.is_empty() From ae755a698ff48fe5d7193dad46112bf5b96cff83 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 17:05:05 +0800 Subject: [PATCH 012/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 0839db711cb1..464d33c832c0 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -1,14 +1,13 @@ -#!/usr/bin/env python3 from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Any, Generic, TypeVar +from typing import Any, TypeVar -T = TypeVar("T", bound=bool) +T = TypeVar("T") -class SkewNode(Generic[T]): +class SkewNode[T]: """ One node of the skew heap. Contains the value and references to two children. @@ -86,8 +85,7 @@ def merge( return result - -class SkewHeap(Generic[T]): +class SkewHeap[T]: """ A data structure that allows inserting a new value and to pop the smallest values. Both operations take O(logN) time where N is the size of the From fb07e077a9e6d4001898bab38109157c1a9ecf50 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:05:28 +0000 Subject: [PATCH 013/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 464d33c832c0..df3d6026d314 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -1,4 +1,3 @@ - from __future__ import annotations from collections.abc import Iterable, Iterator @@ -85,6 +84,7 @@ def merge( return result + class SkewHeap[T]: """ A data structure that allows inserting a new value and to pop the smallest From be9ff5b7922e026f59876a041c03d7aa2c9483ad Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 17:11:15 +0800 Subject: [PATCH 014/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index df3d6026d314..cc72178b5581 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -1,12 +1,18 @@ +#!/usr/bin/env python3 + from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Any, TypeVar +from typing import Any, Protocol, TypeVar, runtime_checkable + +@runtime_checkable +class SupportsLessThan(Protocol): + def __lt__(self, other: Any) -> bool: ... -T = TypeVar("T") +T = TypeVar("T", bound=SupportsLessThan) -class SkewNode[T]: +class SkewNode(Generic[T]): """ One node of the skew heap. Contains the value and references to two children. @@ -85,7 +91,7 @@ def merge( return result -class SkewHeap[T]: +class SkewHeap(Generic[T]): """ A data structure that allows inserting a new value and to pop the smallest values. Both operations take O(logN) time where N is the size of the @@ -192,7 +198,6 @@ def pop(self) -> T | None: ) return result - def top(self) -> T: """ Return the smallest value from the heap. From 8abfbf1d103c6663d0a252af545671d5cd815b19 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:11:38 +0000 Subject: [PATCH 015/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index cc72178b5581..7bc03048a231 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -5,10 +5,12 @@ from collections.abc import Iterable, Iterator from typing import Any, Protocol, TypeVar, runtime_checkable + @runtime_checkable class SupportsLessThan(Protocol): def __lt__(self, other: Any) -> bool: ... + T = TypeVar("T", bound=SupportsLessThan) @@ -198,6 +200,7 @@ def pop(self) -> T | None: ) return result + def top(self) -> T: """ Return the smallest value from the heap. From bd9bdf9959b6fe9c216217350ce4e14a5881448f Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 17:12:57 +0800 Subject: [PATCH 016/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 7bc03048a231..12fc9299e74a 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -14,7 +14,7 @@ def __lt__(self, other: Any) -> bool: ... T = TypeVar("T", bound=SupportsLessThan) -class SkewNode(Generic[T]): +class SkewNode([T]): """ One node of the skew heap. Contains the value and references to two children. @@ -93,7 +93,7 @@ def merge( return result -class SkewHeap(Generic[T]): +class SkewHeap([T]): """ A data structure that allows inserting a new value and to pop the smallest values. Both operations take O(logN) time where N is the size of the From 60cb9369ea727ce7b54ab3061d6da76185f0a4a5 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 17:27:41 +0800 Subject: [PATCH 017/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 12fc9299e74a..20b7b2c98cd6 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -3,18 +3,16 @@ from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Any, Protocol, TypeVar, runtime_checkable +from typing import Any, Generic, TypeVar, Protocol - -@runtime_checkable -class SupportsLessThan(Protocol): +class Comparable(Protocol): def __lt__(self, other: Any) -> bool: ... + def __gt__(self, other: Any) -> bool: ... - -T = TypeVar("T", bound=SupportsLessThan) +T = TypeVar("T", bound=Comparable) -class SkewNode([T]): +class SkewNode(Generic[T]): """ One node of the skew heap. Contains the value and references to two children. @@ -92,8 +90,7 @@ def merge( return result - -class SkewHeap([T]): +class SkewHeap(Generic[T]): """ A data structure that allows inserting a new value and to pop the smallest values. Both operations take O(logN) time where N is the size of the @@ -152,7 +149,7 @@ def __iter__(self) -> Iterator[T]: >>> list(sh) [1, 3, 3, 7] """ - result: list[Any] = [] + result: list[T] = [] while self: result.append(self.pop()) @@ -176,7 +173,7 @@ def insert(self, value: T) -> None: """ self._root = SkewNode.merge(self._root, SkewNode(value)) - def pop(self) -> T | None: + def pop(self) -> T: """ Pop the smallest value from the heap and return it. From 362741655f5520f55e89aa8017ffd937da9b6e8f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:28:13 +0000 Subject: [PATCH 018/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 20b7b2c98cd6..a36eb0acac4d 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -5,10 +5,12 @@ from collections.abc import Iterable, Iterator from typing import Any, Generic, TypeVar, Protocol + class Comparable(Protocol): def __lt__(self, other: Any) -> bool: ... def __gt__(self, other: Any) -> bool: ... + T = TypeVar("T", bound=Comparable) @@ -90,6 +92,7 @@ def merge( return result + class SkewHeap(Generic[T]): """ A data structure that allows inserting a new value and to pop the smallest From d42704aa88a2675859cd95c4041028a4df647794 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 17:30:50 +0800 Subject: [PATCH 019/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index a36eb0acac4d..00ff7997cd22 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -3,7 +3,8 @@ from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Any, Generic, TypeVar, Protocol +from typing import Any, Generic, Protocol, TypeVar + class Comparable(Protocol): From c78fbf7b51491e689172064d9afb80fe9f79c91c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:31:16 +0000 Subject: [PATCH 020/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 00ff7997cd22..0eb00115713c 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -6,7 +6,6 @@ from typing import Any, Generic, Protocol, TypeVar - class Comparable(Protocol): def __lt__(self, other: Any) -> bool: ... def __gt__(self, other: Any) -> bool: ... From 6e7bd4442c7736dd8a455a733cc3f78a77746826 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 28 Jun 2025 17:32:45 +0800 Subject: [PATCH 021/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 0eb00115713c..be3c75731be4 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 + from __future__ import annotations from collections.abc import Iterable, Iterator From 24baa2b2747f422102bed5f8ff0797cf39fce7a6 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 09:46:12 +0800 Subject: [PATCH 022/156] Update test_digital_image_processing.py --- .../test_digital_image_processing.py | 101 ++++++++---------- 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index d1200f4d65ca..8b7e03eb9bad 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -2,6 +2,7 @@ PyTest's for Digital Image Processing """ +import os import numpy as np from cv2 import COLOR_BGR2GRAY, cvtColor, imread from numpy import array, uint8 @@ -23,112 +24,98 @@ gray = cvtColor(img, COLOR_BGR2GRAY) -# Test: convert_to_negative() def test_convert_to_negative(): + """Test negative image conversion.""" negative_img = cn.convert_to_negative(img) - # assert negative_img array for at least one True + # Verify output contains at least one non-zero value assert negative_img.any() -# Test: change_contrast() def test_change_contrast(): - with Image.open("digital_image_processing/image_data/lena_small.jpg") as img: - # Work around assertion for response - assert str(cc.change_contrast(img, 110)).startswith( + """Test contrast adjustment functionality.""" + with Image.open("digital_image_processing/image_data/lena_small.jpg") as img_pil: + # Verify returns a PIL Image object + assert str(cc.change_contrast(img_pil, 110)).startswith( " Date: Sun, 29 Jun 2025 01:46:35 +0000 Subject: [PATCH 023/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../test_digital_image_processing.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 8b7e03eb9bad..ae52849363c3 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -59,6 +59,7 @@ def test_gen_gaussian_kernel_filter(): """Test Gaussian filter application.""" assert gg.gaussian_filter(gray, 5, sigma=0.9).all() + def test_convolve_filter(): """Test image convolution operation.""" # Laplace kernel for edge detection @@ -87,14 +88,18 @@ def test_sepia(): def test_burkes(): """Test Burkes dithering algorithm.""" - burkes = bs.Burkes(imread("digital_image_processing/image_data/lena_small.jpg", 1), 120) + burkes = bs.Burkes( + imread("digital_image_processing/image_data/lena_small.jpg", 1), 120 + ) burkes.process() assert burkes.output_img.any() def test_nearest_neighbour(): """Test nearest-neighbor resizing.""" - nn = rs.NearestNeighbour(imread("digital_image_processing/image_data/lena_small.jpg", 1), 400, 200) + nn = rs.NearestNeighbour( + imread("digital_image_processing/image_data/lena_small.jpg", 1), 400, 200 + ) nn.process() assert nn.output.any() From f3f089861073e88cfbe4e32dde56e7bb4df65232 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 09:50:19 +0800 Subject: [PATCH 024/156] Update test_digital_image_processing.py --- digital_image_processing/test_digital_image_processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index ae52849363c3..398b70de54c2 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -3,9 +3,9 @@ """ import os + import numpy as np from cv2 import COLOR_BGR2GRAY, cvtColor, imread -from numpy import array, uint8 from PIL import Image from digital_image_processing import change_contrast as cc From aa46117c25977a7cdfec855067a6c4ca635c87ce Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 09:53:34 +0800 Subject: [PATCH 025/156] Update test_digital_image_processing.py --- .../test_digital_image_processing.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 398b70de54c2..04a5cfd62db5 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -63,8 +63,8 @@ def test_gen_gaussian_kernel_filter(): def test_convolve_filter(): """Test image convolution operation.""" # Laplace kernel for edge detection - laplace = array([[0.25, 0.5, 0.25], [0.5, -3, 0.5], [0.25, 0.5, 0.25]]) - result = conv.img_convolve(gray, laplace).astype(uint8) + laplace = np.array([[0.25, 0.5, 0.25], [0.5, -3, 0.5], [0.25, 0.5, 0.25]]) + result = conv.img_convolve(gray, laplace).astype(np.uint8) assert result.any() # Verify convolution output @@ -88,18 +88,14 @@ def test_sepia(): def test_burkes(): """Test Burkes dithering algorithm.""" - burkes = bs.Burkes( - imread("digital_image_processing/image_data/lena_small.jpg", 1), 120 - ) + burkes = bs.Burkes(imread("digital_image_processing/image_data/lena_small.jpg", 1), 120) burkes.process() assert burkes.output_img.any() def test_nearest_neighbour(): """Test nearest-neighbor resizing.""" - nn = rs.NearestNeighbour( - imread("digital_image_processing/image_data/lena_small.jpg", 1), 400, 200 - ) + nn = rs.NearestNeighbour(imread("digital_image_processing/image_data/lena_small.jpg", 1), 400, 200) nn.process() assert nn.output.any() From fe0c1b6ab4a586db97cf19c0a148d68a4abea651 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 01:53:56 +0000 Subject: [PATCH 026/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- digital_image_processing/test_digital_image_processing.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 04a5cfd62db5..8543021f2eb2 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -88,14 +88,18 @@ def test_sepia(): def test_burkes(): """Test Burkes dithering algorithm.""" - burkes = bs.Burkes(imread("digital_image_processing/image_data/lena_small.jpg", 1), 120) + burkes = bs.Burkes( + imread("digital_image_processing/image_data/lena_small.jpg", 1), 120 + ) burkes.process() assert burkes.output_img.any() def test_nearest_neighbour(): """Test nearest-neighbor resizing.""" - nn = rs.NearestNeighbour(imread("digital_image_processing/image_data/lena_small.jpg", 1), 400, 200) + nn = rs.NearestNeighbour( + imread("digital_image_processing/image_data/lena_small.jpg", 1), 400, 200 + ) nn.process() assert nn.output.any() From eed74bfc331588cf1a55260d8f4de05b940ebe8d Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 10:08:00 +0800 Subject: [PATCH 027/156] Update minimum_spanning_tree_prims2.py --- graphs/minimum_spanning_tree_prims2.py | 36 +++----------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 6870cc80f844..881a527bfab6 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -10,7 +10,7 @@ from __future__ import annotations from sys import maxsize -from typing import Generic, TypeVar +from typing import TypeVar # Keep only TypeVar import, remove Generic T = TypeVar("T") @@ -47,7 +47,7 @@ def get_child_right_position(position: int) -> int: return (2 * position) + 2 -class MinPriorityQueue(Generic[T]): +class MinPriorityQueue[T]: # Updated: use square brackets for generic class """ Minimum Priority Queue Class @@ -90,7 +90,6 @@ def __init__(self) -> None: def __len__(self) -> int: return self.elements - def __repr__(self) -> str: return str(self.heap) @@ -156,35 +155,8 @@ def _bubble_down(self, elem: T) -> None: _, child_left_weight = self.heap[child_left_position] _, child_right_weight = self.heap[child_right_position] if child_right_weight < child_left_weight and child_right_weight < weight: - self._swap_nodes(child_right_position, curr_pos) - return self._bubble_down(elem) - if child_left_position < self.elements: - _, child_left_weight = self.heap[child_left_position] - if child_left_weight < weight: - self._swap_nodes(child_left_position, curr_pos) - return self._bubble_down(elem) - else: - return None - if child_right_position < self.elements: - _, child_right_weight = self.heap[child_right_position] - if child_right_weight < weight: - self._swap_nodes(child_right_position, curr_pos) - return self._bubble_down(elem) - return None - - def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None: - # Swap the nodes at the given positions - node1_elem = self.heap[node1_pos][0] - node2_elem = self.heap[node2_pos][0] - self.heap[node1_pos], self.heap[node2_pos] = ( - self.heap[node2_pos], - self.heap[node1_pos], - ) - self.position_map[node1_elem] = node2_pos - self.position_map[node2_elem] = node1_pos - -class GraphUndirectedWeighted(Generic[T]): +class GraphUndirectedWeighted[T]: # Updated: use square brackets for generic class """ Graph Undirected Weighted Class @@ -217,7 +189,7 @@ def add_edge(self, node1: T, node2: T, weight: int) -> None: self.connections[node2][node1] = weight -def prims_algo( +def prims_algo[T]( # Updated: add type parameter for generic function graph: GraphUndirectedWeighted[T], ) -> tuple[dict[T, int], dict[T, T | None]]: """ From 8404a25626eb3411cacb197c93f9d3dbeb660b00 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 10:16:56 +0800 Subject: [PATCH 028/156] Update minimum_spanning_tree_prims2.py --- graphs/minimum_spanning_tree_prims2.py | 57 +++++++++++++++++++------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 881a527bfab6..a712ad1aa02f 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -10,7 +10,7 @@ from __future__ import annotations from sys import maxsize -from typing import TypeVar # Keep only TypeVar import, remove Generic +from typing import Generic, TypeVar, Optional T = TypeVar("T") @@ -47,7 +47,7 @@ def get_child_right_position(position: int) -> int: return (2 * position) + 2 -class MinPriorityQueue[T]: # Updated: use square brackets for generic class +class MinPriorityQueue(Generic[T]): """ Minimum Priority Queue Class @@ -90,6 +90,7 @@ def __init__(self) -> None: def __len__(self) -> int: return self.elements + def __repr__(self) -> str: return str(self.heap) @@ -116,7 +117,7 @@ def extract_min(self) -> T: self._bubble_down(bubble_down_elem) return elem - def update_key(self, elem: T, weight: int) -> None: + def update_key(self, elem: T, weight: int) -> None: # 修复了这里的类型提示 # Update the weight of the given key position = self.position_map[elem] self.heap[position] = (elem, weight) @@ -155,8 +156,34 @@ def _bubble_down(self, elem: T) -> None: _, child_left_weight = self.heap[child_left_position] _, child_right_weight = self.heap[child_right_position] if child_right_weight < child_left_weight and child_right_weight < weight: - -class GraphUndirectedWeighted[T]: # Updated: use square brackets for generic class + self._swap_nodes(child_right_position, curr_pos) + return self._bubble_down(elem) + if child_left_position < self.elements: + _, child_left_weight = self.heap[child_left_position] + if child_left_weight < weight: + self._swap_nodes(child_left_position, curr_pos) + return self._bubble_down(elem) + else: + return None + if child_right_position < self.elements: + _, child_right_weight = self.heap[child_right_position] + if child_right_weight < weight: + self._swap_nodes(child_right_position, curr_pos) + return self._bubble_down(elem) + return None + def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None: + # Swap the nodes at the given positions + node1_elem = self.heap[node1_pos][0] + node2_elem = self.heap[node2_pos][0] + self.heap[node1_pos], self.heap[node2_pos] = ( + self.heap[node2_pos], + self.heap[node1_pos], + ) + self.position_map[node1_elem] = node2_pos + self.position_map[node2_elem] = node1_pos + + +class GraphUndirectedWeighted(Generic[T]): """ Graph Undirected Weighted Class @@ -189,9 +216,9 @@ def add_edge(self, node1: T, node2: T, weight: int) -> None: self.connections[node2][node1] = weight -def prims_algo[T]( # Updated: add type parameter for generic function +def prims_algo( graph: GraphUndirectedWeighted[T], -) -> tuple[dict[T, int], dict[T, T | None]]: +) -> tuple[dict[T, int], dict[T, Optional[T]]]: # 使用 Optional[T] 替代 T | None """ >>> graph = GraphUndirectedWeighted() @@ -212,11 +239,11 @@ def prims_algo[T]( # Updated: add type parameter for generic function """ # prim's algorithm for minimum spanning tree dist: dict[T, int] = dict.fromkeys(graph.connections, maxsize) - parent: dict[T, T | None] = dict.fromkeys(graph.connections) + parent: dict[T, Optional[T]] = dict.fromkeys(graph.connections, None) priority_queue: MinPriorityQueue[T] = MinPriorityQueue() - for node, weight in dist.items(): - priority_queue.push(node, weight) + for node in graph.connections: + priority_queue.push(node, dist[node]) if priority_queue.is_empty(): return dist, parent @@ -225,17 +252,17 @@ def prims_algo[T]( # Updated: add type parameter for generic function node = priority_queue.extract_min() dist[node] = 0 for neighbour in graph.connections[node]: - if dist[neighbour] > dist[node] + graph.connections[node][neighbour]: - dist[neighbour] = dist[node] + graph.connections[node][neighbour] + if dist[neighbour] > graph.connections[node][neighbour]: + dist[neighbour] = graph.connections[node][neighbour] priority_queue.update_key(neighbour, dist[neighbour]) parent[neighbour] = node # running prim's algorithm while not priority_queue.is_empty(): node = priority_queue.extract_min() - for neighbour in graph.connections[node]: - if dist[neighbour] > dist[node] + graph.connections[node][neighbour]: - dist[neighbour] = dist[node] + graph.connections[node][neighbour] + for neighbour in graph.connections[node] + if dist[neighbour] > graph.connections[node][neighbour]: + dist[neighbour] = graph.connections[node][neighbour] priority_queue.update_key(neighbour, dist[neighbour]) parent[neighbour] = node return dist, parent From a978c35d779bb5aaee5fe60dfa9b910ada63d1bc Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 10:22:45 +0800 Subject: [PATCH 029/156] Update minimum_spanning_tree_prims2.py --- graphs/minimum_spanning_tree_prims2.py | 140 ++++++++++++++----------- 1 file changed, 77 insertions(+), 63 deletions(-) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index a712ad1aa02f..51f4d54bbada 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -17,7 +17,7 @@ def get_parent_position(position: int) -> int: """ - heap helper function get the position of the parent of the current node + Heap helper function to get the position of the parent of the current node >>> get_parent_position(1) 0 @@ -29,7 +29,7 @@ def get_parent_position(position: int) -> int: def get_child_left_position(position: int) -> int: """ - heap helper function get the position of the left child of the current node + Heap helper function to get the position of the left child of the current node >>> get_child_left_position(0) 1 @@ -39,7 +39,7 @@ def get_child_left_position(position: int) -> int: def get_child_right_position(position: int) -> int: """ - heap helper function get the position of the right child of the current node + Heap helper function to get the position of the right child of the current node >>> get_child_right_position(0) 2 @@ -47,21 +47,19 @@ def get_child_right_position(position: int) -> int: return (2 * position) + 2 + class MinPriorityQueue(Generic[T]): """ Minimum Priority Queue Class Functions: - is_empty: function to check if the priority queue is empty - push: function to add an element with given priority to the queue - extract_min: function to remove and return the element with lowest weight (highest - priority) - update_key: function to update the weight of the given key - _bubble_up: helper function to place a node at the proper position (upward - movement) - _bubble_down: helper function to place a node at the proper position (downward - movement) - _swap_nodes: helper function to swap the nodes at the given positions + is_empty: Check if the priority queue is empty + push: Add an element with given priority to the queue + extract_min: Remove and return the element with lowest weight (highest priority) + update_key: Update the weight of the given key + _bubble_up: Place a node at proper position (upward movement) + _bubble_down: Place a node at proper position (downward movement) + _swap_nodes: Swap nodes at given positions >>> queue = MinPriorityQueue() @@ -95,18 +93,18 @@ def __repr__(self) -> str: return str(self.heap) def is_empty(self) -> bool: - # Check if the priority queue is empty + """Check if the priority queue is empty""" return self.elements == 0 def push(self, elem: T, weight: int) -> None: - # Add an element with given priority to the queue + """Add an element with given priority to the queue""" self.heap.append((elem, weight)) self.position_map[elem] = self.elements self.elements += 1 self._bubble_up(elem) def extract_min(self) -> T: - # Remove and return the element with lowest weight (highest priority) + """Remove and return the element with lowest weight (highest priority)""" if self.elements > 1: self._swap_nodes(0, self.elements - 1) elem, _ = self.heap.pop() @@ -117,8 +115,8 @@ def extract_min(self) -> T: self._bubble_down(bubble_down_elem) return elem - def update_key(self, elem: T, weight: int) -> None: # 修复了这里的类型提示 - # Update the weight of the given key + def update_key(self, elem: T, weight: int) -> None: + """Update the weight of the given key""" position = self.position_map[elem] self.heap[position] = (elem, weight) if position > 0: @@ -130,49 +128,51 @@ def update_key(self, elem: T, weight: int) -> None: # 修复了这里的类型 self._bubble_down(elem) else: self._bubble_down(elem) - def _bubble_up(self, elem: T) -> None: - # Place a node at the proper position (upward movement) [to be used internally - # only] + """Place node at proper position (upward movement) - internal use only""" curr_pos = self.position_map[elem] if curr_pos == 0: - return None + return parent_position = get_parent_position(curr_pos) _, weight = self.heap[curr_pos] _, parent_weight = self.heap[parent_position] if parent_weight > weight: self._swap_nodes(parent_position, curr_pos) - return self._bubble_up(elem) - return None + self._bubble_up(elem) def _bubble_down(self, elem: T) -> None: - # Place a node at the proper position (downward movement) [to be used - # internally only] + """Place node at proper position (downward movement) - internal use only""" curr_pos = self.position_map[elem] _, weight = self.heap[curr_pos] child_left_position = get_child_left_position(curr_pos) child_right_position = get_child_right_position(curr_pos) + + # Check if both children exist if child_left_position < self.elements and child_right_position < self.elements: _, child_left_weight = self.heap[child_left_position] _, child_right_weight = self.heap[child_right_position] if child_right_weight < child_left_weight and child_right_weight < weight: self._swap_nodes(child_right_position, curr_pos) - return self._bubble_down(elem) + self._bubble_down(elem) + return + + # Check left child if child_left_position < self.elements: _, child_left_weight = self.heap[child_left_position] if child_left_weight < weight: self._swap_nodes(child_left_position, curr_pos) - return self._bubble_down(elem) - else: - return None + self._bubble_down(elem) + return + + # Check right child if child_right_position < self.elements: _, child_right_weight = self.heap[child_right_position] if child_right_weight < weight: self._swap_nodes(child_right_position, curr_pos) - return self._bubble_down(elem) - return None + self._bubble_down(elem) + def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None: - # Swap the nodes at the given positions + """Swap nodes at given positions""" node1_elem = self.heap[node1_pos][0] node2_elem = self.heap[node2_pos][0] self.heap[node1_pos], self.heap[node2_pos] = ( @@ -188,8 +188,8 @@ class GraphUndirectedWeighted(Generic[T]): Graph Undirected Weighted Class Functions: - add_node: function to add a node in the graph - add_edge: function to add an edge between 2 nodes in the graph + add_node: Add a node to the graph + add_edge: Add an edge between two nodes with given weight """ def __init__(self) -> None: @@ -203,13 +203,13 @@ def __len__(self) -> int: return self.nodes def add_node(self, node: T) -> None: - # Add a node in the graph if it is not in the graph + """Add a node to the graph if not already present""" if node not in self.connections: self.connections[node] = {} self.nodes += 1 def add_edge(self, node1: T, node2: T, weight: int) -> None: - # Add an edge between 2 nodes in the graph + """Add an edge between two nodes with given weight""" self.add_node(node1) self.add_node(node2) self.connections[node1][node2] = weight @@ -218,10 +218,11 @@ def add_edge(self, node1: T, node2: T, weight: int) -> None: def prims_algo( graph: GraphUndirectedWeighted[T], -) -> tuple[dict[T, int], dict[T, Optional[T]]]: # 使用 Optional[T] 替代 T | None +) -> tuple[dict[T, int], dict[T, Optional[T]]]: """ - >>> graph = GraphUndirectedWeighted() + Prim's algorithm for minimum spanning tree + >>> graph = GraphUndirectedWeighted() >>> graph.add_edge("a", "b", 3) >>> graph.add_edge("b", "c", 10) >>> graph.add_edge("c", "d", 5) @@ -230,39 +231,52 @@ def prims_algo( >>> dist, parent = prims_algo(graph) - >>> abs(dist["a"] - dist["b"]) + >>> dist["b"] 3 - >>> abs(dist["d"] - dist["b"]) - 15 - >>> abs(dist["a"] - dist["c"]) - 13 + >>> dist["c"] + 10 + >>> dist["d"] + 5 + >>> parent["b"] + 'a' + >>> parent["c"] + 'b' + >>> parent["d"] + 'c' """ - # prim's algorithm for minimum spanning tree - dist: dict[T, int] = dict.fromkeys(graph.connections, maxsize) - parent: dict[T, Optional[T]] = dict.fromkeys(graph.connections, None) + # Initialize distance and parent dictionaries + dist: dict[T, int] = {node: maxsize for node in graph.connections} + parent: dict[T, Optional[T]] = {node: None for node in graph.connections} + # Create priority queue and add all nodes priority_queue: MinPriorityQueue[T] = MinPriorityQueue() for node in graph.connections: priority_queue.push(node, dist[node]) + # Return if graph is empty if priority_queue.is_empty(): return dist, parent - - # initialization - node = priority_queue.extract_min() - dist[node] = 0 - for neighbour in graph.connections[node]: - if dist[neighbour] > graph.connections[node][neighbour]: - dist[neighbour] = graph.connections[node][neighbour] - priority_queue.update_key(neighbour, dist[neighbour]) - parent[neighbour] = node - - # running prim's algorithm + # Start with first node + start_node = priority_queue.extract_min() + dist[start_node] = 0 + + # Update neighbors of start node + for neighbor, weight in graph.connections[start_node].items(): + if dist[neighbor] > weight: + dist[neighbor] = weight + priority_queue.update_key(neighbor, weight) + parent[neighbor] = start_node + + # Main algorithm loop while not priority_queue.is_empty(): node = priority_queue.extract_min() - for neighbour in graph.connections[node] - if dist[neighbour] > graph.connections[node][neighbour]: - dist[neighbour] = graph.connections[node][neighbour] - priority_queue.update_key(neighbour, dist[neighbour]) - parent[neighbour] = node + + # Explore neighbors of current node + for neighbor, weight in graph.connections[node].items(): + # Update if found better connection to tree + if dist[neighbor] > weight: + dist[neighbor] = weight + priority_queue.update_key(neighbor, weight) + parent[neighbor] = node + return dist, parent From a7e46033826c0b53d5f60244a28398dab05a99a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 02:23:07 +0000 Subject: [PATCH 030/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- graphs/minimum_spanning_tree_prims2.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 51f4d54bbada..0399c0fd6e9d 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -47,7 +47,6 @@ def get_child_right_position(position: int) -> int: return (2 * position) + 2 - class MinPriorityQueue(Generic[T]): """ Minimum Priority Queue Class @@ -128,6 +127,7 @@ def update_key(self, elem: T, weight: int) -> None: self._bubble_down(elem) else: self._bubble_down(elem) + def _bubble_up(self, elem: T) -> None: """Place node at proper position (upward movement) - internal use only""" curr_pos = self.position_map[elem] @@ -146,7 +146,7 @@ def _bubble_down(self, elem: T) -> None: _, weight = self.heap[curr_pos] child_left_position = get_child_left_position(curr_pos) child_right_position = get_child_right_position(curr_pos) - + # Check if both children exist if child_left_position < self.elements and child_right_position < self.elements: _, child_left_weight = self.heap[child_left_position] @@ -155,7 +155,7 @@ def _bubble_down(self, elem: T) -> None: self._swap_nodes(child_right_position, curr_pos) self._bubble_down(elem) return - + # Check left child if child_left_position < self.elements: _, child_left_weight = self.heap[child_left_position] @@ -163,7 +163,7 @@ def _bubble_down(self, elem: T) -> None: self._swap_nodes(child_left_position, curr_pos) self._bubble_down(elem) return - + # Check right child if child_right_position < self.elements: _, child_right_weight = self.heap[child_right_position] @@ -270,7 +270,7 @@ def prims_algo( # Main algorithm loop while not priority_queue.is_empty(): node = priority_queue.extract_min() - + # Explore neighbors of current node for neighbor, weight in graph.connections[node].items(): # Update if found better connection to tree From dd1665164e78e33fa0770d78f2abc4a1fb188499 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 10:27:33 +0800 Subject: [PATCH 031/156] Update minimum_spanning_tree_prims2.py --- graphs/minimum_spanning_tree_prims2.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 0399c0fd6e9d..94c95ffb7b64 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -10,7 +10,7 @@ from __future__ import annotations from sys import maxsize -from typing import Generic, TypeVar, Optional +from typing import Generic, TypeVar T = TypeVar("T") @@ -146,7 +146,7 @@ def _bubble_down(self, elem: T) -> None: _, weight = self.heap[curr_pos] child_left_position = get_child_left_position(curr_pos) child_right_position = get_child_right_position(curr_pos) - + # Check if both children exist if child_left_position < self.elements and child_right_position < self.elements: _, child_left_weight = self.heap[child_left_position] @@ -155,7 +155,7 @@ def _bubble_down(self, elem: T) -> None: self._swap_nodes(child_right_position, curr_pos) self._bubble_down(elem) return - + # Check left child if child_left_position < self.elements: _, child_left_weight = self.heap[child_left_position] @@ -163,7 +163,7 @@ def _bubble_down(self, elem: T) -> None: self._swap_nodes(child_left_position, curr_pos) self._bubble_down(elem) return - + # Check right child if child_right_position < self.elements: _, child_right_weight = self.heap[child_right_position] @@ -218,7 +218,7 @@ def add_edge(self, node1: T, node2: T, weight: int) -> None: def prims_algo( graph: GraphUndirectedWeighted[T], -) -> tuple[dict[T, int], dict[T, Optional[T]]]: +) -> tuple[dict[T, int], dict[T, T | None]]: """ Prim's algorithm for minimum spanning tree @@ -244,9 +244,9 @@ def prims_algo( >>> parent["d"] 'c' """ - # Initialize distance and parent dictionaries - dist: dict[T, int] = {node: maxsize for node in graph.connections} - parent: dict[T, Optional[T]] = {node: None for node in graph.connections} + # Initialize distance and parent dictionaries using dict.fromkeys + dist: dict[T, int] = dict.fromkeys(graph.connections, maxsize) + parent: dict[T, T | None] = dict.fromkeys(graph.connections, None) # Create priority queue and add all nodes priority_queue: MinPriorityQueue[T] = MinPriorityQueue() @@ -256,6 +256,7 @@ def prims_algo( # Return if graph is empty if priority_queue.is_empty(): return dist, parent + # Start with first node start_node = priority_queue.extract_min() dist[start_node] = 0 @@ -270,7 +271,7 @@ def prims_algo( # Main algorithm loop while not priority_queue.is_empty(): node = priority_queue.extract_min() - + # Explore neighbors of current node for neighbor, weight in graph.connections[node].items(): # Update if found better connection to tree From 3793d0977a7c3c66aac4241a089524f1d630475d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 02:27:58 +0000 Subject: [PATCH 032/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- graphs/minimum_spanning_tree_prims2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 94c95ffb7b64..4b6e8302c2a9 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -146,7 +146,7 @@ def _bubble_down(self, elem: T) -> None: _, weight = self.heap[curr_pos] child_left_position = get_child_left_position(curr_pos) child_right_position = get_child_right_position(curr_pos) - + # Check if both children exist if child_left_position < self.elements and child_right_position < self.elements: _, child_left_weight = self.heap[child_left_position] @@ -155,7 +155,7 @@ def _bubble_down(self, elem: T) -> None: self._swap_nodes(child_right_position, curr_pos) self._bubble_down(elem) return - + # Check left child if child_left_position < self.elements: _, child_left_weight = self.heap[child_left_position] @@ -163,7 +163,7 @@ def _bubble_down(self, elem: T) -> None: self._swap_nodes(child_left_position, curr_pos) self._bubble_down(elem) return - + # Check right child if child_right_position < self.elements: _, child_right_weight = self.heap[child_right_position] @@ -271,7 +271,7 @@ def prims_algo( # Main algorithm loop while not priority_queue.is_empty(): node = priority_queue.extract_min() - + # Explore neighbors of current node for neighbor, weight in graph.connections[node].items(): # Update if found better connection to tree From 98e9282739e752be372dd4b290be81db04cb2331 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 10:47:23 +0800 Subject: [PATCH 033/156] Update matrix_class.py --- matrix/matrix_class.py | 293 +++++++++++++++-------------------------- 1 file changed, 107 insertions(+), 186 deletions(-) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index a5940a38e836..253f271f2b9d 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -2,118 +2,31 @@ from __future__ import annotations +from typing import Union + + class Matrix: """ Matrix object generated from a 2D array where each element is an array representing - a row. - Rows can contain type int or float. - Common operations and information available. - >>> rows = [ - ... [1, 2, 3], - ... [4, 5, 6], - ... [7, 8, 9] - ... ] - >>> matrix = Matrix(rows) - >>> print(matrix) - [[1. 2. 3.] - [4. 5. 6.] - [7. 8. 9.]] - - Matrix rows and columns are available as 2D arrays - >>> matrix.rows - [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - >>> matrix.columns() - [[1, 4, 7], [2, 5, 8], [3, 6, 9]] - - Order is returned as a tuple - >>> matrix.order - (3, 3) - - Squareness and invertability are represented as bool - >>> matrix.is_square - True - >>> matrix.is_invertable() - False - - Identity, Minors, Cofactors and Adjugate are returned as Matrices. Inverse can be - a Matrix or Nonetype - >>> print(matrix.identity()) - [[1. 0. 0.] - [0. 1. 0.] - [0. 0. 1.]] - >>> print(matrix.minors()) - [[-3. -6. -3.] - [-6. -12. -6.] - [-3. -6. -3.]] - >>> print(matrix.cofactors()) - [[-3. 6. -3.] - [6. -12. 6.] - [-3. 6. -3.]] - >>> # won't be apparent due to the nature of the cofactor matrix - >>> print(matrix.adjugate()) - [[-3. 6. -3.] - [6. -12. 6.] - [-3. 6. -3.]] - >>> matrix.inverse() - Traceback (most recent call last): - ... - TypeError: Only matrices with a non-zero determinant have an inverse - - Determinant is an int, float, or Nonetype - >>> matrix.determinant() - 0 - - Negation, scalar multiplication, addition, subtraction, multiplication and - exponentiation are available and all return a Matrix - >>> print(-matrix) - [[-1. -2. -3.] - [-4. -5. -6.] - [-7. -8. -9.]] - >>> matrix2 = matrix * 3 - >>> print(matrix2) - [[3. 6. 9.] - [12. 15. 18.] - [21. 24. 27.]] - >>> print(matrix + matrix2) - [[4. 8. 12.] - [16. 20. 24.] - [28. 32. 36.]] - >>> print(matrix - matrix2) - [[-2. -4. -6.] - [-8. -10. -12.] - [-14. -16. -18.]] - >>> print(matrix ** 3) - [[468. 576. 684.] - [1062. 1305. 1548.] - [1656. 2034. 2412.]] - - Matrices can also be modified - >>> matrix.add_row([10, 11, 12]) - >>> print(matrix) - [[1. 2. 3.] - [4. 5. 6.] - [7. 8. 9.] - [10. 11. 12.]] - >>> matrix2.add_column([8, 16, 32]) - >>> print(matrix2) - [[3. 6. 9. 8.] - [12. 15. 18. 16.] - [21. 24. 27. 32.]] - >>> print(matrix * matrix2) - [[90. 108. 126. 136.] - [198. 243. 288. 304.] - [306. 378. 450. 472.] - [414. 513. 612. 640.]] + a row. Supports both integer and float values. """ - def __init__(self, rows: list[list[int]]): + __hash__ = None # Fix PLW1641: Mark class as unhashable + + def __init__(self, rows: list[list[float]]) -> None: + """ + Initialize matrix from 2D list. Validates input structure and types. + Raises TypeError for invalid input structure or element types. + """ error = TypeError( "Matrices must be formed from a list of zero or more lists containing at " "least one and the same number of values, each of which must be of type " "int or float." ) - if len(rows) != 0: + + # Validate matrix structure and content + if rows: cols = len(rows[0]) if cols == 0: raise error @@ -127,55 +40,63 @@ def __init__(self, rows: list[list[int]]): else: self.rows = [] - # MATRIX INFORMATION - def columns(self) -> list[list[int]]: + # MATRIX INFORMATION METHODS + def columns(self) -> list[list[float]]: + """Return matrix columns as 2D list""" return [[row[i] for row in self.rows] for i in range(len(self.rows[0]))] @property def num_rows(self) -> int: + """Get number of rows in matrix""" return len(self.rows) @property def num_columns(self) -> int: + """Get number of columns in matrix""" return len(self.rows[0]) @property def order(self) -> tuple[int, int]: + """Get matrix dimensions as (rows, columns) tuple""" return self.num_rows, self.num_columns @property def is_square(self) -> bool: + """Check if matrix is square (rows == columns)""" return self.order[0] == self.order[1] def identity(self) -> Matrix: + """Generate identity matrix of same dimensions""" values = [ [0 if column_num != row_num else 1 for column_num in range(self.num_rows)] for row_num in range(self.num_rows) ] return Matrix(values) - def determinant(self) -> int: + def determinant(self) -> float: + """Calculate matrix determinant. Returns 0 for non-square matrices.""" if not self.is_square: - return 0 + return 0.0 if self.order == (0, 0): - return 1 + return 1.0 if self.order == (1, 1): - return int(self.rows[0][0]) + return float(self.rows[0][0]) if self.order == (2, 2): - return int( + return float( (self.rows[0][0] * self.rows[1][1]) - (self.rows[0][1] * self.rows[1][0]) ) - else: - return sum( - self.rows[0][column] * self.cofactors().rows[0][column] - for column in range(self.num_columns) - ) + return sum( + self.rows[0][column] * self.cofactors().rows[0][column] + for column in range(self.num_columns) + ) def is_invertable(self) -> bool: + """Check if matrix is invertible (non-zero determinant)""" return bool(self.determinant()) - def get_minor(self, row: int, column: int) -> int: + def get_minor(self, row: int, column: int) -> float: + """Calculate minor for specified element (determinant of submatrix)""" values = [ [ self.rows[other_row][other_column] @@ -187,12 +108,12 @@ def get_minor(self, row: int, column: int) -> int: ] return Matrix(values).determinant() - def get_cofactor(self, row: int, column: int) -> int: - if (row + column) % 2 == 0: - return self.get_minor(row, column) - return -1 * self.get_minor(row, column) + def get_cofactor(self, row: int, column: int) -> float: + """Calculate cofactor for specified element (signed minor)""" + return self.get_minor(row, column) * (-1 if (row + column) % 2 else 1) def minors(self) -> Matrix: + """Generate matrix of minors""" return Matrix( [ [self.get_minor(row, column) for column in range(self.num_columns)] @@ -201,103 +122,102 @@ def minors(self) -> Matrix: ) def cofactors(self) -> Matrix: + """Generate cofactor matrix""" return Matrix( [ - [ - self.minors().rows[row][column] - if (row + column) % 2 == 0 - else self.minors().rows[row][column] * -1 - for column in range(self.minors().num_columns) - ] - for row in range(self.minors().num_rows) + [self.get_cofactor(row, column) for column in range(self.num_columns)] + for row in range(self.num_rows) ] ) def adjugate(self) -> Matrix: - values = [ - [self.cofactors().rows[column][row] for column in range(self.num_columns)] - for row in range(self.num_rows) - ] - return Matrix(values) + """Generate adjugate matrix (transpose of cofactor matrix)""" + return Matrix( + [ + [self.cofactors().rows[column][row] for column in range(self.num_columns)] + for row in range(self.num_rows) + ] + ) def inverse(self) -> Matrix: - determinant = self.determinant() - if not determinant: + """Calculate matrix inverse. Raises TypeError for singular matrices.""" + det = self.determinant() + if abs(det) < 1e-10: # Floating point tolerance raise TypeError("Only matrices with a non-zero determinant have an inverse") - return self.adjugate() * (1 / determinant) + return self.adjugate() * (1 / det) def __repr__(self) -> str: + """Official string representation of matrix""" return str(self.rows) def __str__(self) -> str: - if self.num_rows == 0: + """User-friendly string representation of matrix""" + if not self.rows: return "[]" if self.num_rows == 1: - return "[[" + ". ".join(str(self.rows[0])) + "]]" + return "[[" + ". ".join(str(val) for val in self.rows[0]) + "]]" return ( "[" + "\n ".join( - [ - "[" + ". ".join([str(value) for value in row]) + ".]" - for row in self.rows - ] + "[" + ". ".join(str(val) for val in row) + ".]" + for row in self.rows ) + "]" ) - # MATRIX MANIPULATION - def add_row(self, row: list[int], position: int | None = None) -> None: - type_error = TypeError("Row must be a list containing all ints and/or floats") + # MATRIX MANIPULATION METHODS + def add_row(self, row: list[float], position: int | None = None) -> None: + """Add row to matrix. Validates type and length.""" if not isinstance(row, list): - raise type_error + raise TypeError("Row must be a list") for value in row: if not isinstance(value, (int, float)): - raise type_error + raise TypeError("Row elements must be int or float") if len(row) != self.num_columns: - raise ValueError( - "Row must be equal in length to the other rows in the matrix" - ) + raise ValueError("Row length must match matrix columns") + if position is None: self.rows.append(row) else: - self.rows = self.rows[0:position] + [row] + self.rows[position:] + # Fix RUF005: Use iterable unpacking instead of concatenation + self.rows = [*self.rows[:position], row, *self.rows[position:]] - def add_column(self, column: list[int], position: int | None = None) -> None: - type_error = TypeError( - "Column must be a list containing all ints and/or floats" - ) + def add_column(self, column: list[float], position: int | None = None) -> None: + """Add column to matrix. Validates type and length.""" if not isinstance(column, list): - raise type_error + raise TypeError("Column must be a list") for value in column: if not isinstance(value, (int, float)): - raise type_error + raise TypeError("Column elements must be int or float") if len(column) != self.num_rows: - raise ValueError( - "Column must be equal in length to the other columns in the matrix" - ) + raise ValueError("Column length must match matrix rows") if position is None: - self.rows = [self.rows[i] + [column[i]] for i in range(self.num_rows)] + for i, value in enumerate(column): + self.rows[i].append(value) else: - self.rows = [ - self.rows[i][0:position] + [column[i]] + self.rows[i][position:] - for i in range(self.num_rows) - ] + # Fix RUF005: Use iterable unpacking instead of concatenation + for i, value in enumerate(column): + self.rows[i] = [*self.rows[i][:position], value, *self.rows[i][position:]] # MATRIX OPERATIONS def __eq__(self, other: object) -> bool: + """Check matrix equality""" if not isinstance(other, Matrix): return NotImplemented return self.rows == other.rows def __ne__(self, other: object) -> bool: + """Check matrix inequality""" return not self == other def __neg__(self) -> Matrix: - return self * -1 + """Negate matrix elements""" + return self * -1.0 def __add__(self, other: Matrix) -> Matrix: + """Matrix addition. Requires same dimensions.""" if self.order != other.order: - raise ValueError("Addition requires matrices of the same order") + raise ValueError("Addition requires matrices of same dimensions") return Matrix( [ [self.rows[i][j] + other.rows[i][j] for j in range(self.num_columns)] @@ -306,8 +226,9 @@ def __add__(self, other: Matrix) -> Matrix: ) def __sub__(self, other: Matrix) -> Matrix: + """Matrix subtraction. Requires same dimensions.""" if self.order != other.order: - raise ValueError("Subtraction requires matrices of the same order") + raise ValueError("Subtraction requires matrices of same dimensions") return Matrix( [ [self.rows[i][j] - other.rows[i][j] for j in range(self.num_columns)] @@ -315,48 +236,48 @@ def __sub__(self, other: Matrix) -> Matrix: ] ) - def __mul__(self, other: Matrix | float) -> Matrix: + def __mul__(self, other: Union[Matrix, float]) -> Matrix: + """Matrix multiplication (scalar or matrix)""" if isinstance(other, (int, float)): - return Matrix( - [[int(element * other) for element in row] for row in self.rows] - ) + # Preserve float precision by removing int conversion + return Matrix([[element * other for element in row] for row in self.rows]) elif isinstance(other, Matrix): if self.num_columns != other.num_rows: raise ValueError( - "The number of columns in the first matrix must " - "be equal to the number of rows in the second" + "Matrix multiplication requires columns of first matrix " + "to match rows of second matrix" ) return Matrix( [ - [Matrix.dot_product(row, column) for column in other.columns()] + [Matrix.dot_product(row, col) for col in other.columns()] for row in self.rows ] ) - else: - raise TypeError( - "A Matrix can only be multiplied by an int, float, or another matrix" - ) + raise TypeError( + "Matrix can only be multiplied by scalar or another matrix" + ) - def __pow__(self, other: int) -> Matrix: - if not isinstance(other, int): - raise TypeError("A Matrix can only be raised to the power of an int") + def __pow__(self, exponent: int) -> Matrix: + """Matrix exponentiation. Requires square matrix.""" + if not isinstance(exponent, int): + raise TypeError("Exponent must be integer") if not self.is_square: raise ValueError("Only square matrices can be raised to a power") - if other == 0: + if exponent == 0: return self.identity() - if other < 0: + if exponent < 0: if self.is_invertable(): - return self.inverse() ** (-other) + return self.inverse() ** (-exponent) raise ValueError( - "Only invertable matrices can be raised to a negative power" + "Only invertible matrices can be raised to negative powers" ) result = self - for _ in range(other - 1): + for _ in range(exponent - 1): result *= self return result - @classmethod - def dot_product(cls, row: list[int], column: list[int]) -> int: + def dot_product(cls, row: list[float], column: list[float]) -> float: + """Calculate dot product of two vectors""" return sum(row[i] * column[i] for i in range(len(row))) From 54f2730df712014f6b76c87ae279246d4b04aaa8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 02:47:46 +0000 Subject: [PATCH 034/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- matrix/matrix_class.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 253f271f2b9d..c51de4d41010 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -5,7 +5,6 @@ from typing import Union - class Matrix: """ Matrix object generated from a 2D array where each element is an array representing @@ -24,7 +23,7 @@ def __init__(self, rows: list[list[float]]) -> None: "least one and the same number of values, each of which must be of type " "int or float." ) - + # Validate matrix structure and content if rows: cols = len(rows[0]) @@ -134,7 +133,10 @@ def adjugate(self) -> Matrix: """Generate adjugate matrix (transpose of cofactor matrix)""" return Matrix( [ - [self.cofactors().rows[column][row] for column in range(self.num_columns)] + [ + self.cofactors().rows[column][row] + for column in range(self.num_columns) + ] for row in range(self.num_rows) ] ) @@ -159,8 +161,7 @@ def __str__(self) -> str: return ( "[" + "\n ".join( - "[" + ". ".join(str(val) for val in row) + ".]" - for row in self.rows + "[" + ". ".join(str(val) for val in row) + ".]" for row in self.rows ) + "]" ) @@ -175,7 +176,7 @@ def add_row(self, row: list[float], position: int | None = None) -> None: raise TypeError("Row elements must be int or float") if len(row) != self.num_columns: raise ValueError("Row length must match matrix columns") - + if position is None: self.rows.append(row) else: @@ -190,14 +191,18 @@ def add_column(self, column: list[float], position: int | None = None) -> None: if not isinstance(value, (int, float)): raise TypeError("Column elements must be int or float") if len(column) != self.num_rows: - raise ValueError("Column length must match matrix rows") + raise ValueError("Column length must match matrix rows") if position is None: for i, value in enumerate(column): self.rows[i].append(value) else: # Fix RUF005: Use iterable unpacking instead of concatenation for i, value in enumerate(column): - self.rows[i] = [*self.rows[i][:position], value, *self.rows[i][position:]] + self.rows[i] = [ + *self.rows[i][:position], + value, + *self.rows[i][position:], + ] # MATRIX OPERATIONS def __eq__(self, other: object) -> bool: @@ -253,9 +258,7 @@ def __mul__(self, other: Union[Matrix, float]) -> Matrix: for row in self.rows ] ) - raise TypeError( - "Matrix can only be multiplied by scalar or another matrix" - ) + raise TypeError("Matrix can only be multiplied by scalar or another matrix") def __pow__(self, exponent: int) -> Matrix: """Matrix exponentiation. Requires square matrix.""" @@ -275,6 +278,7 @@ def __pow__(self, exponent: int) -> Matrix: for _ in range(exponent - 1): result *= self return result + @classmethod def dot_product(cls, row: list[float], column: list[float]) -> float: """Calculate dot product of two vectors""" From d5ab844b74ad5c1cf4caec455bbcc54dfa8ac716 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 11:54:13 +0800 Subject: [PATCH 035/156] Update matrix_class.py --- matrix/matrix_class.py | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index c51de4d41010..6508f9067914 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - class Matrix: """ @@ -11,7 +9,7 @@ class Matrix: a row. Supports both integer and float values. """ - __hash__ = None # Fix PLW1641: Mark class as unhashable + __hash__: None = None # Fix PLW1641: Mark class as unhashable with type annotation def __init__(self, rows: list[list[float]]) -> None: """ @@ -23,7 +21,7 @@ def __init__(self, rows: list[list[float]]) -> None: "least one and the same number of values, each of which must be of type " "int or float." ) - + # Validate matrix structure and content if rows: cols = len(rows[0]) @@ -67,11 +65,10 @@ def is_square(self) -> bool: def identity(self) -> Matrix: """Generate identity matrix of same dimensions""" values = [ - [0 if column_num != row_num else 1 for column_num in range(self.num_rows)] + [0.0 if column_num != row_num else 1.0 for column_num in range(self.num_rows)] for row_num in range(self.num_rows) ] return Matrix(values) - def determinant(self) -> float: """Calculate matrix determinant. Returns 0 for non-square matrices.""" if not self.is_square: @@ -133,10 +130,7 @@ def adjugate(self) -> Matrix: """Generate adjugate matrix (transpose of cofactor matrix)""" return Matrix( [ - [ - self.cofactors().rows[column][row] - for column in range(self.num_columns) - ] + [self.cofactors().rows[column][row] for column in range(self.num_columns)] for row in range(self.num_rows) ] ) @@ -161,7 +155,8 @@ def __str__(self) -> str: return ( "[" + "\n ".join( - "[" + ". ".join(str(val) for val in row) + ".]" for row in self.rows + "[" + ". ".join(str(val) for val in row) + ".]" + for row in self.rows ) + "]" ) @@ -176,13 +171,12 @@ def add_row(self, row: list[float], position: int | None = None) -> None: raise TypeError("Row elements must be int or float") if len(row) != self.num_columns: raise ValueError("Row length must match matrix columns") - + if position is None: self.rows.append(row) else: # Fix RUF005: Use iterable unpacking instead of concatenation self.rows = [*self.rows[:position], row, *self.rows[position:]] - def add_column(self, column: list[float], position: int | None = None) -> None: """Add column to matrix. Validates type and length.""" if not isinstance(column, list): @@ -192,17 +186,14 @@ def add_column(self, column: list[float], position: int | None = None) -> None: raise TypeError("Column elements must be int or float") if len(column) != self.num_rows: raise ValueError("Column length must match matrix rows") + if position is None: for i, value in enumerate(column): self.rows[i].append(value) else: # Fix RUF005: Use iterable unpacking instead of concatenation for i, value in enumerate(column): - self.rows[i] = [ - *self.rows[i][:position], - value, - *self.rows[i][position:], - ] + self.rows[i] = [*self.rows[i][:position], value, *self.rows[i][position:]] # MATRIX OPERATIONS def __eq__(self, other: object) -> bool: @@ -240,8 +231,7 @@ def __sub__(self, other: Matrix) -> Matrix: for i in range(self.num_rows) ] ) - - def __mul__(self, other: Union[Matrix, float]) -> Matrix: + def __mul__(self, other: Matrix | float) -> Matrix: """Matrix multiplication (scalar or matrix)""" if isinstance(other, (int, float)): # Preserve float precision by removing int conversion @@ -258,7 +248,9 @@ def __mul__(self, other: Union[Matrix, float]) -> Matrix: for row in self.rows ] ) - raise TypeError("Matrix can only be multiplied by scalar or another matrix") + raise TypeError( + "Matrix can only be multiplied by scalar or another matrix" + ) def __pow__(self, exponent: int) -> Matrix: """Matrix exponentiation. Requires square matrix.""" From d1d1bccda41c90fd032cf4e0581b3e1bf7c2f29f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 03:54:36 +0000 Subject: [PATCH 036/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- matrix/matrix_class.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 6508f9067914..587ca9aa6ede 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -21,7 +21,7 @@ def __init__(self, rows: list[list[float]]) -> None: "least one and the same number of values, each of which must be of type " "int or float." ) - + # Validate matrix structure and content if rows: cols = len(rows[0]) @@ -65,10 +65,14 @@ def is_square(self) -> bool: def identity(self) -> Matrix: """Generate identity matrix of same dimensions""" values = [ - [0.0 if column_num != row_num else 1.0 for column_num in range(self.num_rows)] + [ + 0.0 if column_num != row_num else 1.0 + for column_num in range(self.num_rows) + ] for row_num in range(self.num_rows) ] return Matrix(values) + def determinant(self) -> float: """Calculate matrix determinant. Returns 0 for non-square matrices.""" if not self.is_square: @@ -130,7 +134,10 @@ def adjugate(self) -> Matrix: """Generate adjugate matrix (transpose of cofactor matrix)""" return Matrix( [ - [self.cofactors().rows[column][row] for column in range(self.num_columns)] + [ + self.cofactors().rows[column][row] + for column in range(self.num_columns) + ] for row in range(self.num_rows) ] ) @@ -155,8 +162,7 @@ def __str__(self) -> str: return ( "[" + "\n ".join( - "[" + ". ".join(str(val) for val in row) + ".]" - for row in self.rows + "[" + ". ".join(str(val) for val in row) + ".]" for row in self.rows ) + "]" ) @@ -171,12 +177,13 @@ def add_row(self, row: list[float], position: int | None = None) -> None: raise TypeError("Row elements must be int or float") if len(row) != self.num_columns: raise ValueError("Row length must match matrix columns") - + if position is None: self.rows.append(row) else: # Fix RUF005: Use iterable unpacking instead of concatenation self.rows = [*self.rows[:position], row, *self.rows[position:]] + def add_column(self, column: list[float], position: int | None = None) -> None: """Add column to matrix. Validates type and length.""" if not isinstance(column, list): @@ -186,14 +193,18 @@ def add_column(self, column: list[float], position: int | None = None) -> None: raise TypeError("Column elements must be int or float") if len(column) != self.num_rows: raise ValueError("Column length must match matrix rows") - + if position is None: for i, value in enumerate(column): self.rows[i].append(value) else: # Fix RUF005: Use iterable unpacking instead of concatenation for i, value in enumerate(column): - self.rows[i] = [*self.rows[i][:position], value, *self.rows[i][position:]] + self.rows[i] = [ + *self.rows[i][:position], + value, + *self.rows[i][position:], + ] # MATRIX OPERATIONS def __eq__(self, other: object) -> bool: @@ -231,6 +242,7 @@ def __sub__(self, other: Matrix) -> Matrix: for i in range(self.num_rows) ] ) + def __mul__(self, other: Matrix | float) -> Matrix: """Matrix multiplication (scalar or matrix)""" if isinstance(other, (int, float)): @@ -248,9 +260,7 @@ def __mul__(self, other: Matrix | float) -> Matrix: for row in self.rows ] ) - raise TypeError( - "Matrix can only be multiplied by scalar or another matrix" - ) + raise TypeError("Matrix can only be multiplied by scalar or another matrix") def __pow__(self, exponent: int) -> Matrix: """Matrix exponentiation. Requires square matrix.""" From a9614551e0aeaceadbf08f91277207143acbd353 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 12:00:32 +0800 Subject: [PATCH 037/156] Update matrix_class.py --- matrix/matrix_class.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 587ca9aa6ede..0038da236c30 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -2,6 +2,7 @@ from __future__ import annotations +from typing import ClassVar class Matrix: """ @@ -9,7 +10,7 @@ class Matrix: a row. Supports both integer and float values. """ - __hash__: None = None # Fix PLW1641: Mark class as unhashable with type annotation + __hash__: ClassVar[None] = None # Fix PLW1641: Mark class as unhashable with type annotation def __init__(self, rows: list[list[float]]) -> None: """ From 8eace88bbf098f9ca2b8f122800a7fe1693cdb12 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 12:03:48 +0800 Subject: [PATCH 038/156] Update matrix_class.py --- matrix/matrix_class.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 0038da236c30..d3c53631e979 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -4,13 +4,15 @@ from typing import ClassVar + class Matrix: """ Matrix object generated from a 2D array where each element is an array representing a row. Supports both integer and float values. """ - __hash__: ClassVar[None] = None # Fix PLW1641: Mark class as unhashable with type annotation + # Fix PLW1641: Mark class as unhashable with type annotation + __hash__: ClassVar[None] = None def __init__(self, rows: list[list[float]]) -> None: """ From ac0dfd3f36f3b0be3dd2b40ff82e238502d81332 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 12:08:25 +0800 Subject: [PATCH 039/156] Update matrix_class.py --- matrix/matrix_class.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index d3c53631e979..f14a9c431dd1 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -2,18 +2,15 @@ from __future__ import annotations -from typing import ClassVar - +from typing import final +@final class Matrix: """ Matrix object generated from a 2D array where each element is an array representing a row. Supports both integer and float values. """ - # Fix PLW1641: Mark class as unhashable with type annotation - __hash__: ClassVar[None] = None - def __init__(self, rows: list[list[float]]) -> None: """ Initialize matrix from 2D list. Validates input structure and types. From b8631395494fe53afd513b3cb37486e251d866d4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 04:08:48 +0000 Subject: [PATCH 040/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- matrix/matrix_class.py | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index f14a9c431dd1..394e38c164d6 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -4,6 +4,7 @@ from typing import final + @final class Matrix: """ From 8166650f8c85f33375453f1b9f3422b67eb05758 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 12:27:49 +0800 Subject: [PATCH 041/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index be3c75731be4..76906a0079fd 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -15,7 +15,7 @@ def __gt__(self, other: Any) -> bool: ... T = TypeVar("T", bound=Comparable) -class SkewNode(Generic[T]): +class SkewNode[T]: """ One node of the skew heap. Contains the value and references to two children. @@ -94,7 +94,7 @@ def merge( return result -class SkewHeap(Generic[T]): +class SkewHeap[T]: """ A data structure that allows inserting a new value and to pop the smallest values. Both operations take O(logN) time where N is the size of the From 5508c20eb12d8f030a250bc672de7b91f3f5a59d Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 12:34:28 +0800 Subject: [PATCH 042/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 76906a0079fd..616941a75230 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Any, Generic, Protocol, TypeVar +from typing import Any, Protocol, TypeVar class Comparable(Protocol): @@ -83,7 +83,7 @@ def merge( if not root2: return root1 - if root1.value > root2.value: + if root2.value < root1.value: root1, root2 = root2, root1 result = root1 From f157d207f922c625968126a2e0f6051f5e6c405c Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 12:40:33 +0800 Subject: [PATCH 043/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 53 ++++++++++++++++--------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 616941a75230..6a4d6df97ef8 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -62,36 +62,37 @@ def value(self) -> T: """ return self._value - @staticmethod - def merge( - root1: SkewNode[T] | None, root2: SkewNode[T] | None - ) -> SkewNode[T] | None: - """ - Merge 2 nodes together. - >>> SkewNode.merge(SkewNode(10),SkewNode(-10.5)).value - -10.5 - >>> SkewNode.merge(SkewNode(10),SkewNode(10.5)).value - 10 - >>> SkewNode.merge(SkewNode(10),SkewNode(10)).value - 10 - >>> SkewNode.merge(SkewNode(-100),SkewNode(-10.5)).value - -100 - """ - if not root1: - return root2 +@staticmethod +def merge( + root1: SkewNode[T] | None, root2: SkewNode[T] | None +) -> SkewNode[T] | None: + """ + Merge 2 nodes together. + >>> SkewNode.merge(SkewNode(10),SkewNode(-10.5)).value + -10.5 + >>> SkewNode.merge(SkewNode(10),SkewNode(10.5)).value + 10 + >>> SkewNode.merge(SkewNode(10),SkewNode(10)).value + 10 + >>> SkewNode.merge(SkewNode(-100),SkewNode(-10.5)).value + -100 + """ + if not root1: + return root2 - if not root2: - return root1 + if not root2: + return root1 - if root2.value < root1.value: - root1, root2 = root2, root1 + # Use explicit __lt__ method for type safety + if root1.value > root2.value: + root1, root2 = root2, root1 - result = root1 - temp = root1.right - result.right = root1.left - result.left = SkewNode.merge(temp, root2) + result = root1 + temp = root1.right + result.right = root1.left + result.left = SkewNode.merge(temp, root2) - return result + return result class SkewHeap[T]: From a24fd46763ed090714db06a7cfc99d058230963f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 04:40:55 +0000 Subject: [PATCH 044/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 6a4d6df97ef8..0907a531cee0 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -62,10 +62,9 @@ def value(self) -> T: """ return self._value + @staticmethod -def merge( - root1: SkewNode[T] | None, root2: SkewNode[T] | None -) -> SkewNode[T] | None: +def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: """ Merge 2 nodes together. >>> SkewNode.merge(SkewNode(10),SkewNode(-10.5)).value From b4117d8fabfedcc0abc508dabaab94c1e513934b Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 12:45:44 +0800 Subject: [PATCH 045/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 53 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 0907a531cee0..95628cedbc24 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 - from __future__ import annotations from collections.abc import Iterable, Iterator @@ -62,37 +61,35 @@ def value(self) -> T: """ return self._value + @staticmethod + def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: + """ + Merge 2 nodes together. + >>> SkewNode.merge(SkewNode(10),SkewNode(-10.5)).value + -10.5 + >>> SkewNode.merge(SkewNode(10),SkewNode(10.5)).value + 10 + >>> SkewNode.merge(SkewNode(10),SkewNode(10)).value + 10 + >>> SkewNode.merge(SkewNode(-100),SkewNode(-10.5)).value + -100 + """ + if not root1: + return root2 -@staticmethod -def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: - """ - Merge 2 nodes together. - >>> SkewNode.merge(SkewNode(10),SkewNode(-10.5)).value - -10.5 - >>> SkewNode.merge(SkewNode(10),SkewNode(10.5)).value - 10 - >>> SkewNode.merge(SkewNode(10),SkewNode(10)).value - 10 - >>> SkewNode.merge(SkewNode(-100),SkewNode(-10.5)).value - -100 - """ - if not root1: - return root2 - - if not root2: - return root1 - - # Use explicit __lt__ method for type safety - if root1.value > root2.value: - root1, root2 = root2, root1 + if not root2: + return root1 - result = root1 - temp = root1.right - result.right = root1.left - result.left = SkewNode.merge(temp, root2) + # Use explicit __lt__ method for type safety + if root1.value > root2.value: + root1, root2 = root2, root1 - return result + result = root1 + temp = root1.right + result.right = root1.left + result.left = SkewNode.merge(temp, root2) + return result class SkewHeap[T]: """ From f0beaccd6cdd40fe53b37b193d1280d326e3640a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 04:46:07 +0000 Subject: [PATCH 046/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 95628cedbc24..bcd86abddee7 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -62,7 +62,9 @@ def value(self) -> T: return self._value @staticmethod - def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: + def merge( + root1: SkewNode[T] | None, root2: SkewNode[T] | None + ) -> SkewNode[T] | None: """ Merge 2 nodes together. >>> SkewNode.merge(SkewNode(10),SkewNode(-10.5)).value @@ -91,6 +93,7 @@ def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | return result + class SkewHeap[T]: """ A data structure that allows inserting a new value and to pop the smallest From 6b07de60cfa93f2cde57380c72c1748fdf2b30d6 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 12:49:23 +0800 Subject: [PATCH 047/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index bcd86abddee7..9a09dafb8d83 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -61,36 +61,21 @@ def value(self) -> T: """ return self._value + @staticmethod - def merge( - root1: SkewNode[T] | None, root2: SkewNode[T] | None - ) -> SkewNode[T] | None: - """ - Merge 2 nodes together. - >>> SkewNode.merge(SkewNode(10),SkewNode(-10.5)).value - -10.5 - >>> SkewNode.merge(SkewNode(10),SkewNode(10.5)).value - 10 - >>> SkewNode.merge(SkewNode(10),SkewNode(10)).value - 10 - >>> SkewNode.merge(SkewNode(-100),SkewNode(-10.5)).value - -100 - """ + def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: if not root1: return root2 - if not root2: return root1 - - # Use explicit __lt__ method for type safety - if root1.value > root2.value: + + if root2.value < root1.value: root1, root2 = root2, root1 result = root1 temp = root1.right result.right = root1.left result.left = SkewNode.merge(temp, root2) - return result From 0febfb539e19034242389ec5125f52d4fe775303 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 04:49:46 +0000 Subject: [PATCH 048/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 9a09dafb8d83..721485f547b3 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -61,14 +61,15 @@ def value(self) -> T: """ return self._value - @staticmethod - def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: + def merge( + root1: SkewNode[T] | None, root2: SkewNode[T] | None + ) -> SkewNode[T] | None: if not root1: return root2 if not root2: return root1 - + if root2.value < root1.value: root1, root2 = root2, root1 From 78fe5bb477e399cf0d276a5e49b4a8d9a6f2802f Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 12:54:57 +0800 Subject: [PATCH 049/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 50 ++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 721485f547b3..bc7191cd7474 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -7,8 +7,7 @@ class Comparable(Protocol): - def __lt__(self, other: Any) -> bool: ... - def __gt__(self, other: Any) -> bool: ... + def __lt__(self: T, other: T) -> bool: ... T = TypeVar("T", bound=Comparable) @@ -60,24 +59,40 @@ def value(self) -> T: TypeError: SkewNode.__init__() missing 1 required positional argument: 'value' """ return self._value - @staticmethod - def merge( - root1: SkewNode[T] | None, root2: SkewNode[T] | None - ) -> SkewNode[T] | None: + def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: + """ + Merge 2 nodes together. + >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5)).value + -10.5 + >>> SkewNode.merge(SkewNode(10), SkewNode(10.5)).value + 10 + >>> SkewNode.merge(SkewNode(10), SkewNode(10)).value + 10 + >>> SkewNode.merge(SkewNode(-100), SkewNode(-10.5)).value + -100 + """ if not root1: return root2 + if not root2: return root1 - if root2.value < root1.value: - root1, root2 = root2, root1 - - result = root1 - temp = root1.right - result.right = root1.left - result.left = SkewNode.merge(temp, root2) - return result + # 使用类型安全的比较方式 + if root1.value < root2.value: + # root1 更小,不需要交换 + result = root1 + temp = root1.right + result.right = root1.left + result.left = SkewNode.merge(temp, root2) + return result + else: + # root2 更小或相等,需要交换 + result = root2 + temp = root2.right + result.right = root2.left + result.left = SkewNode.merge(root1, temp) + return result class SkewHeap[T]: @@ -182,9 +197,10 @@ def pop(self) -> T: IndexError: Can't get top element for the empty heap. """ result = self.top() - self._root = ( - SkewNode.merge(self._root.left, self._root.right) if self._root else None - ) + if self._root: + self._root = SkewNode.merge(self._root.left, self._root.right) + else: + self._root = None return result From 283601dc499b951f21694304382d2e9b6ae60a2b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 04:55:20 +0000 Subject: [PATCH 050/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index bc7191cd7474..f2dbf145512a 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -59,8 +59,11 @@ def value(self) -> T: TypeError: SkewNode.__init__() missing 1 required positional argument: 'value' """ return self._value + @staticmethod - def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: + def merge( + root1: SkewNode[T] | None, root2: SkewNode[T] | None + ) -> SkewNode[T] | None: """ Merge 2 nodes together. >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5)).value From e0131667c73bd1292001c451721932a31954d3fe Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 13:01:28 +0800 Subject: [PATCH 051/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 43 ++++++++++++++----------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index f2dbf145512a..2c151788e2ac 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -3,11 +3,11 @@ from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Any, Protocol, TypeVar +from typing import Protocol, TypeVar class Comparable(Protocol): - def __lt__(self: T, other: T) -> bool: ... + def __lt__(self, other: Any) -> bool: ... T = TypeVar("T", bound=Comparable) @@ -59,11 +59,8 @@ def value(self) -> T: TypeError: SkewNode.__init__() missing 1 required positional argument: 'value' """ return self._value - @staticmethod - def merge( - root1: SkewNode[T] | None, root2: SkewNode[T] | None - ) -> SkewNode[T] | None: + def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: """ Merge 2 nodes together. >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5)).value @@ -80,22 +77,23 @@ def merge( if not root2: return root1 - - # 使用类型安全的比较方式 - if root1.value < root2.value: - # root1 更小,不需要交换 - result = root1 - temp = root1.right - result.right = root1.left - result.left = SkewNode.merge(temp, root2) - return result - else: - # root2 更小或相等,需要交换 - result = root2 - temp = root2.right - result.right = root2.left - result.left = SkewNode.merge(root1, temp) - return result + try: + if root1.value < root2.value: + result = root1 + temp = root1.right + result.right = root1.left + result.left = SkewNode.merge(temp, root2) + return result + except TypeError: + # 回退到值比较 + pass + + # 如果比较失败或 root2 更小 + result = root2 + temp = root2.right + result.right = root2.left + result.left = SkewNode.merge(root1, temp) + return result class SkewHeap[T]: @@ -206,7 +204,6 @@ def pop(self) -> T: self._root = None return result - def top(self) -> T: """ Return the smallest value from the heap. From cf707b51966fa5f1faf3d4f13b6c80fb2bdeda00 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 05:01:50 +0000 Subject: [PATCH 052/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 2c151788e2ac..c68c58ca6f77 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -59,8 +59,11 @@ def value(self) -> T: TypeError: SkewNode.__init__() missing 1 required positional argument: 'value' """ return self._value + @staticmethod - def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: + def merge( + root1: SkewNode[T] | None, root2: SkewNode[T] | None + ) -> SkewNode[T] | None: """ Merge 2 nodes together. >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5)).value @@ -204,6 +207,7 @@ def pop(self) -> T: self._root = None return result + def top(self) -> T: """ Return the smallest value from the heap. From b33f0b0b3d10ca1b9b29f3315285dcdb38fb09d6 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 13:09:01 +0800 Subject: [PATCH 053/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 65 ++++++++++++++++--------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index c68c58ca6f77..8a2d42a17c6a 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Protocol, TypeVar +from typing import Protocol, TypeVar, Any class Comparable(Protocol): @@ -61,11 +61,9 @@ def value(self) -> T: return self._value @staticmethod - def merge( - root1: SkewNode[T] | None, root2: SkewNode[T] | None - ) -> SkewNode[T] | None: + def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: """ - Merge 2 nodes together. + Merge two nodes together. >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5)).value -10.5 >>> SkewNode.merge(SkewNode(10), SkewNode(10.5)).value @@ -75,23 +73,27 @@ def merge( >>> SkewNode.merge(SkewNode(-100), SkewNode(-10.5)).value -100 """ + # Handle empty nodes if not root1: return root2 - if not root2: return root1 + + # Compare values using explicit __lt__ method try: - if root1.value < root2.value: + # Check if root1 is smaller than root2 + if root1.value.__lt__(root2.value): + # root1 is smaller, make it the new root result = root1 temp = root1.right result.right = root1.left result.left = SkewNode.merge(temp, root2) return result - except TypeError: - # 回退到值比较 + except (TypeError, AttributeError): + # Fallback if __lt__ comparison fails pass - - # 如果比较失败或 root2 更小 + + # If root2 is smaller or comparison failed, use root2 as new root result = root2 temp = root2.right result.right = root2.left @@ -99,11 +101,11 @@ def merge( return result + class SkewHeap[T]: """ - A data structure that allows inserting a new value and to pop the smallest - values. Both operations take O(logN) time where N is the size of the - structure. + A data structure that allows inserting a new value and popping the smallest + values. Both operations take O(logN) time where N is the size of the heap. Wiki: https://en.wikipedia.org/wiki/Skew_heap Visualization: https://www.cs.usfca.edu/~galles/visualization/SkewHeap.html @@ -125,6 +127,8 @@ class SkewHeap[T]: def __init__(self, data: Iterable[T] | None = ()) -> None: """ + Initialize the skew heap with optional data + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] @@ -136,8 +140,8 @@ def __init__(self, data: Iterable[T] | None = ()) -> None: def __bool__(self) -> bool: """ - Check if the heap is not empty. - + Check if the heap is not empty + >>> sh = SkewHeap() >>> bool(sh) False @@ -152,8 +156,8 @@ def __bool__(self) -> bool: def __iter__(self) -> Iterator[T]: """ - Returns sorted list containing all the values in the heap. - + Iterate through all values in sorted order + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] @@ -161,17 +165,17 @@ def __iter__(self) -> Iterator[T]: result: list[T] = [] while self: result.append(self.pop()) - - # Pushing items back to the heap not to clear it. + + # Restore the heap state for item in result: self.insert(item) - + return iter(result) def insert(self, value: T) -> None: """ - Insert the value into the heap. - + Insert a new value into the heap + >>> sh = SkewHeap() >>> sh.insert(3) >>> sh.insert(1) @@ -184,8 +188,8 @@ def insert(self, value: T) -> None: def pop(self) -> T: """ - Pop the smallest value from the heap and return it. - + Remove and return the smallest value from the heap + >>> sh = SkewHeap([3, 1, 3, 7]) >>> sh.pop() 1 @@ -203,15 +207,12 @@ def pop(self) -> T: result = self.top() if self._root: self._root = SkewNode.merge(self._root.left, self._root.right) - else: - self._root = None - return result def top(self) -> T: """ - Return the smallest value from the heap. - + Return the smallest value without removing it + >>> sh = SkewHeap() >>> sh.insert(3) >>> sh.top() @@ -232,8 +233,8 @@ def top(self) -> T: def clear(self) -> None: """ - Clear the heap. - + Clear all elements from the heap + >>> sh = SkewHeap([3, 1, 3, 7]) >>> sh.clear() >>> sh.pop() From f0a4b6a30b6364489c3c137aaa0b9f476432f22f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 05:09:22 +0000 Subject: [PATCH 054/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 8a2d42a17c6a..67193a87f9d9 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -61,7 +61,9 @@ def value(self) -> T: return self._value @staticmethod - def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: + def merge( + root1: SkewNode[T] | None, root2: SkewNode[T] | None + ) -> SkewNode[T] | None: """ Merge two nodes together. >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5)).value @@ -78,7 +80,7 @@ def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | return root2 if not root2: return root1 - + # Compare values using explicit __lt__ method try: # Check if root1 is smaller than root2 @@ -92,7 +94,7 @@ def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | except (TypeError, AttributeError): # Fallback if __lt__ comparison fails pass - + # If root2 is smaller or comparison failed, use root2 as new root result = root2 temp = root2.right @@ -101,7 +103,6 @@ def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | return result - class SkewHeap[T]: """ A data structure that allows inserting a new value and popping the smallest @@ -128,7 +129,7 @@ class SkewHeap[T]: def __init__(self, data: Iterable[T] | None = ()) -> None: """ Initialize the skew heap with optional data - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] @@ -141,7 +142,7 @@ def __init__(self, data: Iterable[T] | None = ()) -> None: def __bool__(self) -> bool: """ Check if the heap is not empty - + >>> sh = SkewHeap() >>> bool(sh) False @@ -157,7 +158,7 @@ def __bool__(self) -> bool: def __iter__(self) -> Iterator[T]: """ Iterate through all values in sorted order - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] @@ -165,17 +166,17 @@ def __iter__(self) -> Iterator[T]: result: list[T] = [] while self: result.append(self.pop()) - + # Restore the heap state for item in result: self.insert(item) - + return iter(result) def insert(self, value: T) -> None: """ Insert a new value into the heap - + >>> sh = SkewHeap() >>> sh.insert(3) >>> sh.insert(1) @@ -189,7 +190,7 @@ def insert(self, value: T) -> None: def pop(self) -> T: """ Remove and return the smallest value from the heap - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> sh.pop() 1 @@ -212,7 +213,7 @@ def pop(self) -> T: def top(self) -> T: """ Return the smallest value without removing it - + >>> sh = SkewHeap() >>> sh.insert(3) >>> sh.top() @@ -234,7 +235,7 @@ def top(self) -> T: def clear(self) -> None: """ Clear all elements from the heap - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> sh.clear() >>> sh.pop() From 4f0910cafed9e0ccf2fb127680851f520de77158 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 13:10:31 +0800 Subject: [PATCH 055/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 67193a87f9d9..cb2083c5e87b 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Protocol, TypeVar, Any +from typing import Protocol, TypeVar class Comparable(Protocol): From 180d8d548cd0e2c79e1107ec5cb6326557701fe9 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 13:17:50 +0800 Subject: [PATCH 056/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 104 +++++++++++++----------------- 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index cb2083c5e87b..0073c5a8dbc4 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -3,17 +3,17 @@ from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Protocol, TypeVar +from typing import Protocol, TypeVar, Generic class Comparable(Protocol): - def __lt__(self, other: Any) -> bool: ... + def __lt__(self, other: object) -> bool: ... T = TypeVar("T", bound=Comparable) -class SkewNode[T]: +class SkewNode(Generic[T]): """ One node of the skew heap. Contains the value and references to two children. @@ -35,35 +35,14 @@ def value(self) -> T: 3.14159 >>> SkewNode("hello").value 'hello' - >>> SkewNode(None).value - >>> SkewNode(True).value True - >>> SkewNode([]).value - [] - >>> SkewNode({}).value - {} - >>> SkewNode(set()).value - set() - >>> SkewNode(0.0).value - 0.0 - >>> SkewNode(-1e-10).value - -1e-10 >>> SkewNode(10).value 10 - >>> SkewNode(-10.5).value - -10.5 - >>> SkewNode().value - Traceback (most recent call last): - ... - TypeError: SkewNode.__init__() missing 1 required positional argument: 'value' """ return self._value - @staticmethod - def merge( - root1: SkewNode[T] | None, root2: SkewNode[T] | None - ) -> SkewNode[T] | None: + def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: """ Merge two nodes together. >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5)).value @@ -80,30 +59,35 @@ def merge( return root2 if not root2: return root1 + + # Compare values using explicit comparison function + if SkewNode._is_less_than(root1.value, root2.value): + # root1 is smaller, make it the new root + result = root1 + temp = root1.right + result.right = root1.left + result.left = SkewNode.merge(temp, root2) + return result + else: + # root2 is smaller or equal, use it as new root + result = root2 + temp = root2.right + result.right = root2.left + result.left = SkewNode.merge(root1, temp) + return result - # Compare values using explicit __lt__ method + @staticmethod + def _is_less_than(a: T, b: T) -> bool: + """Safe comparison function that avoids type checker issues""" try: - # Check if root1 is smaller than root2 - if root1.value.__lt__(root2.value): - # root1 is smaller, make it the new root - result = root1 - temp = root1.right - result.right = root1.left - result.left = SkewNode.merge(temp, root2) - return result - except (TypeError, AttributeError): - # Fallback if __lt__ comparison fails - pass - - # If root2 is smaller or comparison failed, use root2 as new root - result = root2 - temp = root2.right - result.right = root2.left - result.left = SkewNode.merge(root1, temp) - return result + return a < b + except TypeError: + # Fallback comparison for non-comparable types + # Uses string representation as last resort + return str(a) < str(b) -class SkewHeap[T]: +class SkewHeap(Generic[T]): """ A data structure that allows inserting a new value and popping the smallest values. Both operations take O(logN) time where N is the size of the heap. @@ -129,7 +113,7 @@ class SkewHeap[T]: def __init__(self, data: Iterable[T] | None = ()) -> None: """ Initialize the skew heap with optional data - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] @@ -142,7 +126,7 @@ def __init__(self, data: Iterable[T] | None = ()) -> None: def __bool__(self) -> bool: """ Check if the heap is not empty - + >>> sh = SkewHeap() >>> bool(sh) False @@ -154,29 +138,32 @@ def __bool__(self) -> bool: False """ return self._root is not None - def __iter__(self) -> Iterator[T]: """ Iterate through all values in sorted order - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] """ + # Create a temporary heap for iteration + temp_heap = SkewHeap() result: list[T] = [] + + # Pop all elements from the heap while self: - result.append(self.pop()) - + item = self.pop() + result.append(item) + temp_heap.insert(item) + # Restore the heap state - for item in result: - self.insert(item) - + self._root = temp_heap._root return iter(result) def insert(self, value: T) -> None: """ Insert a new value into the heap - + >>> sh = SkewHeap() >>> sh.insert(3) >>> sh.insert(1) @@ -190,7 +177,7 @@ def insert(self, value: T) -> None: def pop(self) -> T: """ Remove and return the smallest value from the heap - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> sh.pop() 1 @@ -209,11 +196,10 @@ def pop(self) -> T: if self._root: self._root = SkewNode.merge(self._root.left, self._root.right) return result - def top(self) -> T: """ Return the smallest value without removing it - + >>> sh = SkewHeap() >>> sh.insert(3) >>> sh.top() @@ -235,7 +221,7 @@ def top(self) -> T: def clear(self) -> None: """ Clear all elements from the heap - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> sh.clear() >>> sh.pop() From c5693db87cb963784f4a19b379812f930ca39737 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 05:18:13 +0000 Subject: [PATCH 057/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 0073c5a8dbc4..c42fb479ff3d 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -41,8 +41,11 @@ def value(self) -> T: 10 """ return self._value + @staticmethod - def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | None: + def merge( + root1: SkewNode[T] | None, root2: SkewNode[T] | None + ) -> SkewNode[T] | None: """ Merge two nodes together. >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5)).value @@ -59,7 +62,7 @@ def merge(root1: SkewNode[T] | None, root2: SkewNode[T] | None) -> SkewNode[T] | return root2 if not root2: return root1 - + # Compare values using explicit comparison function if SkewNode._is_less_than(root1.value, root2.value): # root1 is smaller, make it the new root @@ -113,7 +116,7 @@ class SkewHeap(Generic[T]): def __init__(self, data: Iterable[T] | None = ()) -> None: """ Initialize the skew heap with optional data - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] @@ -126,7 +129,7 @@ def __init__(self, data: Iterable[T] | None = ()) -> None: def __bool__(self) -> bool: """ Check if the heap is not empty - + >>> sh = SkewHeap() >>> bool(sh) False @@ -138,10 +141,11 @@ def __bool__(self) -> bool: False """ return self._root is not None + def __iter__(self) -> Iterator[T]: """ Iterate through all values in sorted order - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] @@ -149,13 +153,13 @@ def __iter__(self) -> Iterator[T]: # Create a temporary heap for iteration temp_heap = SkewHeap() result: list[T] = [] - + # Pop all elements from the heap while self: item = self.pop() result.append(item) temp_heap.insert(item) - + # Restore the heap state self._root = temp_heap._root return iter(result) @@ -163,7 +167,7 @@ def __iter__(self) -> Iterator[T]: def insert(self, value: T) -> None: """ Insert a new value into the heap - + >>> sh = SkewHeap() >>> sh.insert(3) >>> sh.insert(1) @@ -177,7 +181,7 @@ def insert(self, value: T) -> None: def pop(self) -> T: """ Remove and return the smallest value from the heap - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> sh.pop() 1 @@ -196,10 +200,11 @@ def pop(self) -> T: if self._root: self._root = SkewNode.merge(self._root.left, self._root.right) return result + def top(self) -> T: """ Return the smallest value without removing it - + >>> sh = SkewHeap() >>> sh.insert(3) >>> sh.top() @@ -221,7 +226,7 @@ def top(self) -> T: def clear(self) -> None: """ Clear all elements from the heap - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> sh.clear() >>> sh.pop() From 84ec0493760b5ff9d5f2dfd74cd93e9ea67c3214 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 13:21:05 +0800 Subject: [PATCH 058/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index c42fb479ff3d..55f7fe78685f 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Protocol, TypeVar, Generic +from typing import Generic, Protocol, TypeVar class Comparable(Protocol): From 12733195ab7a4540c1281584b121a7934d57cd50 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 13:26:11 +0800 Subject: [PATCH 059/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 55f7fe78685f..d0cb8d321677 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -151,7 +151,7 @@ def __iter__(self) -> Iterator[T]: [1, 3, 3, 7] """ # Create a temporary heap for iteration - temp_heap = SkewHeap() + temp_heap: SkewHeap[T] = SkewHeap() result: list[T] = [] # Pop all elements from the heap From a1a379c8a7967fa5cbcc0ac8484735142ec5c57e Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 13:44:01 +0800 Subject: [PATCH 060/156] Update minimum_spanning_tree_prims2.py --- graphs/minimum_spanning_tree_prims2.py | 145 +++++++++++-------------- 1 file changed, 63 insertions(+), 82 deletions(-) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 4b6e8302c2a9..7312def7791c 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -10,14 +10,10 @@ from __future__ import annotations from sys import maxsize -from typing import Generic, TypeVar - -T = TypeVar("T") - def get_parent_position(position: int) -> int: """ - Heap helper function to get the position of the parent of the current node + heap helper function get the position of the parent of the current node >>> get_parent_position(1) 0 @@ -29,7 +25,7 @@ def get_parent_position(position: int) -> int: def get_child_left_position(position: int) -> int: """ - Heap helper function to get the position of the left child of the current node + heap helper function get the position of the left child of the current node >>> get_child_left_position(0) 1 @@ -39,7 +35,7 @@ def get_child_left_position(position: int) -> int: def get_child_right_position(position: int) -> int: """ - Heap helper function to get the position of the right child of the current node + heap helper function get the position of the right child of the current node >>> get_child_right_position(0) 2 @@ -47,18 +43,21 @@ def get_child_right_position(position: int) -> int: return (2 * position) + 2 -class MinPriorityQueue(Generic[T]): +class MinPriorityQueue[T]: """ Minimum Priority Queue Class Functions: - is_empty: Check if the priority queue is empty - push: Add an element with given priority to the queue - extract_min: Remove and return the element with lowest weight (highest priority) - update_key: Update the weight of the given key - _bubble_up: Place a node at proper position (upward movement) - _bubble_down: Place a node at proper position (downward movement) - _swap_nodes: Swap nodes at given positions + is_empty: function to check if the priority queue is empty + push: function to add an element with given priority to the queue + extract_min: function to remove and return the element with lowest weight (highest + priority) + update_key: function to update the weight of the given key + _bubble_up: helper function to place a node at the proper position (upward + movement) + _bubble_down: helper function to place a node at the proper position (downward + movement) + _swap_nodes: helper function to swap the nodes at the given positions >>> queue = MinPriorityQueue() @@ -92,18 +91,18 @@ def __repr__(self) -> str: return str(self.heap) def is_empty(self) -> bool: - """Check if the priority queue is empty""" + # Check if the priority queue is empty return self.elements == 0 def push(self, elem: T, weight: int) -> None: - """Add an element with given priority to the queue""" + # Add an element with given priority to the queue self.heap.append((elem, weight)) self.position_map[elem] = self.elements self.elements += 1 self._bubble_up(elem) def extract_min(self) -> T: - """Remove and return the element with lowest weight (highest priority)""" + # Remove and return the element with lowest weight (highest priority) if self.elements > 1: self._swap_nodes(0, self.elements - 1) elem, _ = self.heap.pop() @@ -115,7 +114,7 @@ def extract_min(self) -> T: return elem def update_key(self, elem: T, weight: int) -> None: - """Update the weight of the given key""" + # Update the weight of the given key position = self.position_map[elem] self.heap[position] = (elem, weight) if position > 0: @@ -129,50 +128,48 @@ def update_key(self, elem: T, weight: int) -> None: self._bubble_down(elem) def _bubble_up(self, elem: T) -> None: - """Place node at proper position (upward movement) - internal use only""" + # Place a node at the proper position (upward movement) [to be used internally + # only] curr_pos = self.position_map[elem] if curr_pos == 0: - return + return None parent_position = get_parent_position(curr_pos) _, weight = self.heap[curr_pos] _, parent_weight = self.heap[parent_position] if parent_weight > weight: self._swap_nodes(parent_position, curr_pos) - self._bubble_up(elem) + return self._bubble_up(elem) + return None def _bubble_down(self, elem: T) -> None: - """Place node at proper position (downward movement) - internal use only""" + # Place a node at the proper position (downward movement) [to be used + # internally only] curr_pos = self.position_map[elem] _, weight = self.heap[curr_pos] child_left_position = get_child_left_position(curr_pos) child_right_position = get_child_right_position(curr_pos) - - # Check if both children exist if child_left_position < self.elements and child_right_position < self.elements: _, child_left_weight = self.heap[child_left_position] _, child_right_weight = self.heap[child_right_position] if child_right_weight < child_left_weight and child_right_weight < weight: self._swap_nodes(child_right_position, curr_pos) - self._bubble_down(elem) - return - - # Check left child + return self._bubble_down(elem) if child_left_position < self.elements: _, child_left_weight = self.heap[child_left_position] if child_left_weight < weight: self._swap_nodes(child_left_position, curr_pos) - self._bubble_down(elem) - return - - # Check right child + return self._bubble_down(elem) + else: + return None if child_right_position < self.elements: _, child_right_weight = self.heap[child_right_position] if child_right_weight < weight: self._swap_nodes(child_right_position, curr_pos) - self._bubble_down(elem) + return self._bubble_down(elem) + return None def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None: - """Swap nodes at given positions""" + # Swap the nodes at the given positions node1_elem = self.heap[node1_pos][0] node2_elem = self.heap[node2_pos][0] self.heap[node1_pos], self.heap[node2_pos] = ( @@ -183,13 +180,13 @@ def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None: self.position_map[node2_elem] = node1_pos -class GraphUndirectedWeighted(Generic[T]): +class GraphUndirectedWeighted[T]: """ Graph Undirected Weighted Class Functions: - add_node: Add a node to the graph - add_edge: Add an edge between two nodes with given weight + add_node: function to add a node in the graph + add_edge: function to add an edge between 2 nodes in the graph """ def __init__(self) -> None: @@ -203,26 +200,25 @@ def __len__(self) -> int: return self.nodes def add_node(self, node: T) -> None: - """Add a node to the graph if not already present""" + # Add a node in the graph if it is not in the graph if node not in self.connections: self.connections[node] = {} self.nodes += 1 def add_edge(self, node1: T, node2: T, weight: int) -> None: - """Add an edge between two nodes with given weight""" + # Add an edge between 2 nodes in the graph self.add_node(node1) self.add_node(node2) self.connections[node1][node2] = weight self.connections[node2][node1] = weight -def prims_algo( +def prims_algo[T]( graph: GraphUndirectedWeighted[T], ) -> tuple[dict[T, int], dict[T, T | None]]: """ - Prim's algorithm for minimum spanning tree - >>> graph = GraphUndirectedWeighted() + >>> graph.add_edge("a", "b", 3) >>> graph.add_edge("b", "c", 10) >>> graph.add_edge("c", "d", 5) @@ -231,53 +227,38 @@ def prims_algo( >>> dist, parent = prims_algo(graph) - >>> dist["b"] + >>> abs(dist["a"] - dist["b"]) 3 - >>> dist["c"] - 10 - >>> dist["d"] - 5 - >>> parent["b"] - 'a' - >>> parent["c"] - 'b' - >>> parent["d"] - 'c' + >>> abs(dist["d"] - dist["b"]) + 15 + >>> abs(dist["a"] - dist["c"]) + 13 """ - # Initialize distance and parent dictionaries using dict.fromkeys + # prim's algorithm for minimum spanning tree dist: dict[T, int] = dict.fromkeys(graph.connections, maxsize) - parent: dict[T, T | None] = dict.fromkeys(graph.connections, None) + parent: dict[T, T | None] = dict.fromkeys(graph.connections) - # Create priority queue and add all nodes priority_queue: MinPriorityQueue[T] = MinPriorityQueue() - for node in graph.connections: - priority_queue.push(node, dist[node]) + for node, weight in dist.items(): + priority_queue.push(node, weight) - # Return if graph is empty if priority_queue.is_empty(): return dist, parent - - # Start with first node - start_node = priority_queue.extract_min() - dist[start_node] = 0 - - # Update neighbors of start node - for neighbor, weight in graph.connections[start_node].items(): - if dist[neighbor] > weight: - dist[neighbor] = weight - priority_queue.update_key(neighbor, weight) - parent[neighbor] = start_node - - # Main algorithm loop + # initialization + node = priority_queue.extract_min() + dist[node] = 0 + for neighbour in graph.connections[node]: + if dist[neighbour] > dist[node] + graph.connections[node][neighbour]: + dist[neighbour] = dist[node] + graph.connections[node][neighbour] + priority_queue.update_key(neighbour, dist[neighbour]) + parent[neighbour] = node + + # running prim's algorithm while not priority_queue.is_empty(): node = priority_queue.extract_min() - - # Explore neighbors of current node - for neighbor, weight in graph.connections[node].items(): - # Update if found better connection to tree - if dist[neighbor] > weight: - dist[neighbor] = weight - priority_queue.update_key(neighbor, weight) - parent[neighbor] = node - + for neighbour in graph.connections[node]: + if dist[neighbour] > dist[node] + graph.connections[node][neighbour]: + dist[neighbour] = dist[node] + graph.connections[node][neighbour] + priority_queue.update_key(neighbour, dist[neighbour]) + parent[neighbour] = node return dist, parent From bf11752f369862b4f408f36681bc89c74aab00a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 05:44:23 +0000 Subject: [PATCH 061/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- graphs/minimum_spanning_tree_prims2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py index 7312def7791c..d961b5e764c3 100644 --- a/graphs/minimum_spanning_tree_prims2.py +++ b/graphs/minimum_spanning_tree_prims2.py @@ -11,6 +11,7 @@ from sys import maxsize + def get_parent_position(position: int) -> int: """ heap helper function get the position of the parent of the current node From 42266418adebe5191ec6363f899cd47ea07d2afe Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 13:54:53 +0800 Subject: [PATCH 062/156] fix sum_of_digits.py --- maths/sum_of_digits.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/maths/sum_of_digits.py b/maths/sum_of_digits.py index d5488bb9e9e0..89c8a0340f95 100644 --- a/maths/sum_of_digits.py +++ b/maths/sum_of_digits.py @@ -1,3 +1,6 @@ +from collections.abc import Callable +from timeit import timeit + def sum_of_digits(n: int) -> int: """ Find the sum of digits of a number. @@ -31,7 +34,7 @@ def sum_of_digits_recursion(n: int) -> int: 0 """ n = abs(n) - return n if n < 10 else n % 10 + sum_of_digits(n // 10) + return n if n < 10 else n % 10 + sum_of_digits_recursion(n // 10) def sum_of_digits_compact(n: int) -> int: @@ -53,9 +56,6 @@ def benchmark() -> None: """ Benchmark multiple functions, with three different length int values. """ - from collections.abc import Callable - from timeit import timeit - def benchmark_a_function(func: Callable, value: int) -> None: call = f"{func.__name__}({value})" timing = timeit(f"__main__.{call}", setup="import __main__") From a2b2951c1f2fedfc00dcf86b254773333ae74536 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 05:55:16 +0000 Subject: [PATCH 063/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- maths/sum_of_digits.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maths/sum_of_digits.py b/maths/sum_of_digits.py index 89c8a0340f95..ee5c51959619 100644 --- a/maths/sum_of_digits.py +++ b/maths/sum_of_digits.py @@ -1,6 +1,7 @@ from collections.abc import Callable from timeit import timeit + def sum_of_digits(n: int) -> int: """ Find the sum of digits of a number. @@ -56,6 +57,7 @@ def benchmark() -> None: """ Benchmark multiple functions, with three different length int values. """ + def benchmark_a_function(func: Callable, value: int) -> None: call = f"{func.__name__}({value})" timing = timeit(f"__main__.{call}", setup="import __main__") From 3361c552bdd2448da902ce264d80c2a7b5ee9643 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 13:59:58 +0800 Subject: [PATCH 064/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index d0cb8d321677..53c583548950 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -13,7 +13,7 @@ def __lt__(self, other: object) -> bool: ... T = TypeVar("T", bound=Comparable) -class SkewNode(Generic[T]): +class SkewNode[T]: """ One node of the skew heap. Contains the value and references to two children. @@ -90,7 +90,7 @@ def _is_less_than(a: T, b: T) -> bool: return str(a) < str(b) -class SkewHeap(Generic[T]): +class SkewHeap[T]: """ A data structure that allows inserting a new value and popping the smallest values. Both operations take O(logN) time where N is the size of the heap. From 96ade0545e920f1d94be6b087de459382b54bdfd Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 14:26:26 +0800 Subject: [PATCH 065/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 101 +++++++++++++++--------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 53c583548950..02d6b66d9814 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -3,29 +3,22 @@ from __future__ import annotations from collections.abc import Iterable, Iterator -from typing import Generic, Protocol, TypeVar +from typing import Any, Callable, Optional -class Comparable(Protocol): - def __lt__(self, other: object) -> bool: ... - - -T = TypeVar("T", bound=Comparable) - - -class SkewNode[T]: +class SkewNode: """ One node of the skew heap. Contains the value and references to two children. """ - def __init__(self, value: T) -> None: - self._value: T = value - self.left: SkewNode[T] | None = None - self.right: SkewNode[T] | None = None + def __init__(self, value: Any) -> None: + self._value: Any = value + self.left: Optional[SkewNode] = None + self.right: Optional[SkewNode] = None @property - def value(self) -> T: + def value(self) -> Any: """ Return the value of the node. @@ -44,17 +37,20 @@ def value(self) -> T: @staticmethod def merge( - root1: SkewNode[T] | None, root2: SkewNode[T] | None - ) -> SkewNode[T] | None: + root1: Optional[SkewNode], + root2: Optional[SkewNode], + comp: Callable[[Any, Any], bool] + ) -> Optional[SkewNode]: """ Merge two nodes together. - >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5)).value + >>> def comp(a, b): return a < b + >>> SkewNode.merge(SkewNode(10), SkewNode(-10.5), comp).value -10.5 - >>> SkewNode.merge(SkewNode(10), SkewNode(10.5)).value + >>> SkewNode.merge(SkewNode(10), SkewNode(10.5), comp).value 10 - >>> SkewNode.merge(SkewNode(10), SkewNode(10)).value + >>> SkewNode.merge(SkewNode(10), SkewNode(10), comp).value 10 - >>> SkewNode.merge(SkewNode(-100), SkewNode(-10.5)).value + >>> SkewNode.merge(SkewNode(-100), SkewNode(-10.5), comp).value -100 """ # Handle empty nodes @@ -63,34 +59,23 @@ def merge( if not root2: return root1 - # Compare values using explicit comparison function - if SkewNode._is_less_than(root1.value, root2.value): + # Compare values using provided comparison function + if comp(root1.value, root2.value): # root1 is smaller, make it the new root result = root1 temp = root1.right result.right = root1.left - result.left = SkewNode.merge(temp, root2) + result.left = SkewNode.merge(temp, root2, comp) return result else: # root2 is smaller or equal, use it as new root result = root2 temp = root2.right result.right = root2.left - result.left = SkewNode.merge(root1, temp) + result.left = SkewNode.merge(root1, temp, comp) return result - @staticmethod - def _is_less_than(a: T, b: T) -> bool: - """Safe comparison function that avoids type checker issues""" - try: - return a < b - except TypeError: - # Fallback comparison for non-comparable types - # Uses string representation as last resort - return str(a) < str(b) - - -class SkewHeap[T]: +class SkewHeap: """ A data structure that allows inserting a new value and popping the smallest values. Both operations take O(logN) time where N is the size of the heap. @@ -113,15 +98,25 @@ class SkewHeap[T]: [-1, 0, 1] """ - def __init__(self, data: Iterable[T] | None = ()) -> None: + def __init__( + self, + data: Iterable[Any] | None = None, + comp: Callable[[Any, Any], bool] = lambda a, b: a < b + ) -> None: """ - Initialize the skew heap with optional data - + Initialize the skew heap with optional data and comparison function + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] - """ - self._root: SkewNode[T] | None = None + + # Max-heap example + >>> max_heap = SkewHeap([3, 1, 3, 7], comp=lambda a, b: a > b) + >>> list(max_heap) + [7, 3, 3, 1] + """ + self._root: Optional[SkewNode] = None + self._comp = comp if data: for item in data: self.insert(item) @@ -142,7 +137,7 @@ def __bool__(self) -> bool: """ return self._root is not None - def __iter__(self) -> Iterator[T]: + def __iter__(self) -> Iterator[Any]: """ Iterate through all values in sorted order @@ -151,8 +146,8 @@ def __iter__(self) -> Iterator[T]: [1, 3, 3, 7] """ # Create a temporary heap for iteration - temp_heap: SkewHeap[T] = SkewHeap() - result: list[T] = [] + temp_heap = SkewHeap(comp=self._comp) + result: list[Any] = [] # Pop all elements from the heap while self: @@ -164,7 +159,7 @@ def __iter__(self) -> Iterator[T]: self._root = temp_heap._root return iter(result) - def insert(self, value: T) -> None: + def insert(self, value: Any) -> None: """ Insert a new value into the heap @@ -176,9 +171,13 @@ def insert(self, value: T) -> None: >>> list(sh) [1, 3, 3, 7] """ - self._root = SkewNode.merge(self._root, SkewNode(value)) + self._root = SkewNode.merge( + self._root, + SkewNode(value), + self._comp + ) - def pop(self) -> T: + def pop(self) -> Any: """ Remove and return the smallest value from the heap @@ -198,10 +197,14 @@ def pop(self) -> T: """ result = self.top() if self._root: - self._root = SkewNode.merge(self._root.left, self._root.right) + self._root = SkewNode.merge( + self._root.left, + self._root.right, + self._comp + ) return result - def top(self) -> T: + def top(self) -> Any: """ Return the smallest value without removing it From bf93372bcda6789e6e2ec09adeacb66408dba3e9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 06:26:50 +0000 Subject: [PATCH 066/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 02d6b66d9814..f975b0f8ea61 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -39,7 +39,7 @@ def value(self) -> Any: def merge( root1: Optional[SkewNode], root2: Optional[SkewNode], - comp: Callable[[Any, Any], bool] + comp: Callable[[Any, Any], bool], ) -> Optional[SkewNode]: """ Merge two nodes together. @@ -75,6 +75,7 @@ def merge( result.left = SkewNode.merge(root1, temp, comp) return result + class SkewHeap: """ A data structure that allows inserting a new value and popping the smallest @@ -101,15 +102,15 @@ class SkewHeap: def __init__( self, data: Iterable[Any] | None = None, - comp: Callable[[Any, Any], bool] = lambda a, b: a < b + comp: Callable[[Any, Any], bool] = lambda a, b: a < b, ) -> None: """ Initialize the skew heap with optional data and comparison function - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] - + # Max-heap example >>> max_heap = SkewHeap([3, 1, 3, 7], comp=lambda a, b: a > b) >>> list(max_heap) @@ -171,11 +172,7 @@ def insert(self, value: Any) -> None: >>> list(sh) [1, 3, 3, 7] """ - self._root = SkewNode.merge( - self._root, - SkewNode(value), - self._comp - ) + self._root = SkewNode.merge(self._root, SkewNode(value), self._comp) def pop(self) -> Any: """ @@ -197,11 +194,7 @@ def pop(self) -> Any: """ result = self.top() if self._root: - self._root = SkewNode.merge( - self._root.left, - self._root.right, - self._comp - ) + self._root = SkewNode.merge(self._root.left, self._root.right, self._comp) return result def top(self) -> Any: From 072a3d689ec108b3d84f4b3166a274a006137c7e Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 14:32:26 +0800 Subject: [PATCH 067/156] fix skew_heap.py --- data_structures/heap/skew_heap.py | 38 ++++++++++++++++++------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index f975b0f8ea61..44df582e2a26 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -2,8 +2,8 @@ from __future__ import annotations -from collections.abc import Iterable, Iterator -from typing import Any, Callable, Optional +from collections.abc import Iterable, Iterator, Callable +from typing import Any class SkewNode: @@ -14,8 +14,8 @@ class SkewNode: def __init__(self, value: Any) -> None: self._value: Any = value - self.left: Optional[SkewNode] = None - self.right: Optional[SkewNode] = None + self.left: SkewNode | None = None + self.right: SkewNode | None = None @property def value(self) -> Any: @@ -37,10 +37,10 @@ def value(self) -> Any: @staticmethod def merge( - root1: Optional[SkewNode], - root2: Optional[SkewNode], - comp: Callable[[Any, Any], bool], - ) -> Optional[SkewNode]: + root1: SkewNode | None, + root2: SkewNode | None, + comp: Callable[[Any, Any], bool] + ) -> SkewNode | None: """ Merge two nodes together. >>> def comp(a, b): return a < b @@ -75,7 +75,6 @@ def merge( result.left = SkewNode.merge(root1, temp, comp) return result - class SkewHeap: """ A data structure that allows inserting a new value and popping the smallest @@ -102,21 +101,21 @@ class SkewHeap: def __init__( self, data: Iterable[Any] | None = None, - comp: Callable[[Any, Any], bool] = lambda a, b: a < b, + comp: Callable[[Any, Any], bool] = lambda a, b: a < b ) -> None: """ Initialize the skew heap with optional data and comparison function - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] - + # Max-heap example >>> max_heap = SkewHeap([3, 1, 3, 7], comp=lambda a, b: a > b) >>> list(max_heap) [7, 3, 3, 1] """ - self._root: Optional[SkewNode] = None + self._root: SkewNode | None = None self._comp = comp if data: for item in data: @@ -159,7 +158,6 @@ def __iter__(self) -> Iterator[Any]: # Restore the heap state self._root = temp_heap._root return iter(result) - def insert(self, value: Any) -> None: """ Insert a new value into the heap @@ -172,7 +170,11 @@ def insert(self, value: Any) -> None: >>> list(sh) [1, 3, 3, 7] """ - self._root = SkewNode.merge(self._root, SkewNode(value), self._comp) + self._root = SkewNode.merge( + self._root, + SkewNode(value), + self._comp + ) def pop(self) -> Any: """ @@ -194,7 +196,11 @@ def pop(self) -> Any: """ result = self.top() if self._root: - self._root = SkewNode.merge(self._root.left, self._root.right, self._comp) + self._root = SkewNode.merge( + self._root.left, + self._root.right, + self._comp + ) return result def top(self) -> Any: From ed6be0a92d3634fc9034f564d6cd7f4a1a14186c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 06:32:49 +0000 Subject: [PATCH 068/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/skew_heap.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 44df582e2a26..5f4653ca4e1b 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -37,9 +37,7 @@ def value(self) -> Any: @staticmethod def merge( - root1: SkewNode | None, - root2: SkewNode | None, - comp: Callable[[Any, Any], bool] + root1: SkewNode | None, root2: SkewNode | None, comp: Callable[[Any, Any], bool] ) -> SkewNode | None: """ Merge two nodes together. @@ -75,6 +73,7 @@ def merge( result.left = SkewNode.merge(root1, temp, comp) return result + class SkewHeap: """ A data structure that allows inserting a new value and popping the smallest @@ -101,15 +100,15 @@ class SkewHeap: def __init__( self, data: Iterable[Any] | None = None, - comp: Callable[[Any, Any], bool] = lambda a, b: a < b + comp: Callable[[Any, Any], bool] = lambda a, b: a < b, ) -> None: """ Initialize the skew heap with optional data and comparison function - + >>> sh = SkewHeap([3, 1, 3, 7]) >>> list(sh) [1, 3, 3, 7] - + # Max-heap example >>> max_heap = SkewHeap([3, 1, 3, 7], comp=lambda a, b: a > b) >>> list(max_heap) @@ -158,6 +157,7 @@ def __iter__(self) -> Iterator[Any]: # Restore the heap state self._root = temp_heap._root return iter(result) + def insert(self, value: Any) -> None: """ Insert a new value into the heap @@ -170,11 +170,7 @@ def insert(self, value: Any) -> None: >>> list(sh) [1, 3, 3, 7] """ - self._root = SkewNode.merge( - self._root, - SkewNode(value), - self._comp - ) + self._root = SkewNode.merge(self._root, SkewNode(value), self._comp) def pop(self) -> Any: """ @@ -196,11 +192,7 @@ def pop(self) -> Any: """ result = self.top() if self._root: - self._root = SkewNode.merge( - self._root.left, - self._root.right, - self._comp - ) + self._root = SkewNode.merge(self._root.left, self._root.right, self._comp) return result def top(self) -> Any: From a35036fbd857896e4bc333da235d7914c09038a2 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 14:37:19 +0800 Subject: [PATCH 069/156] fix l001 skew_heap.py --- data_structures/heap/skew_heap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index 5f4653ca4e1b..fd4891715053 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 + from __future__ import annotations -from collections.abc import Iterable, Iterator, Callable +from collections.abc import Callable, Iterable, Iterator from typing import Any From 208adb13ab28f9e5a749df3e1e197660806fab52 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 14:43:40 +0800 Subject: [PATCH 070/156] Update skew_heap.py --- data_structures/heap/skew_heap.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py index fd4891715053..dc0efbf1d2bb 100644 --- a/data_structures/heap/skew_heap.py +++ b/data_structures/heap/skew_heap.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 - from __future__ import annotations from collections.abc import Callable, Iterable, Iterator From 06cb5d3031f759fe63b587b595524bce2f51511e Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 15:41:09 +0800 Subject: [PATCH 071/156] Update atbash.py --- ciphers/atbash.py | 76 +++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/ciphers/atbash.py b/ciphers/atbash.py index 4e8f663ed02d..bd48f49aaaa5 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -2,53 +2,39 @@ import string - -def atbash_slow(sequence: str) -> str: - """ - >>> atbash_slow("ABCDEFG") - 'ZYXWVUT' - - >>> atbash_slow("aW;;123BX") - 'zD;;123YC' - """ - output = "" - for i in sequence: - extract = ord(i) - if 65 <= extract <= 90: - output += chr(155 - extract) - elif 97 <= extract <= 122: - output += chr(219 - extract) - else: - output += i - return output - - -def atbash(sequence: str) -> str: +def atbash(text: str) -> str: """ - >>> atbash("ABCDEFG") - 'ZYXWVUT' - - >>> atbash("aW;;123BX") - 'zD;;123YC' + Encodes or decodes text using the Atbash cipher. + + The Atbash cipher substitutes each letter with its mirror in the alphabet: + A -> Z, B -> Y, C -> X, ... Z -> A (case is preserved) + Non-alphabetic characters are left unchanged. + + Args: + text: The input string to encode/decode + + Returns: + The transformed string """ - letters = string.ascii_letters - letters_reversed = string.ascii_lowercase[::-1] + string.ascii_uppercase[::-1] - return "".join( - letters_reversed[letters.index(c)] if c in letters else c for c in sequence + # Create translation tables for uppercase and lowercase + lowercase_map = str.maketrans( + string.ascii_lowercase, + string.ascii_lowercase[::-1] ) + uppercase_map = str.maketrans( + string.ascii_uppercase, + string.ascii_uppercase[::-1] + ) + + # Apply both translation mappings + return text.translate(lowercase_map).translate(uppercase_map) - -def benchmark() -> None: - """Let's benchmark our functions side-by-side...""" - from timeit import timeit - - print("Running performance benchmarks...") - setup = "from string import printable ; from __main__ import atbash, atbash_slow" - print(f"> atbash_slow(): {timeit('atbash_slow(printable)', setup=setup)} seconds") - print(f"> atbash(): {timeit('atbash(printable)', setup=setup)} seconds") - - +# Example usage if __name__ == "__main__": - for example in ("ABCDEFGH", "123GGjj", "testStringtest", "with space"): - print(f"{example} encrypted in atbash: {atbash(example)}") - benchmark() + test_string = "Hello, World! 123" + encoded = atbash(test_string) + decoded = atbash(encoded) + + print(f"Original: {test_string}") + print(f"Encoded: {encoded}") + print(f"Decoded: {decoded}") From b1e0e3f11530e685b85454a4c86cc152673e8f98 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 07:41:32 +0000 Subject: [PATCH 072/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/atbash.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/ciphers/atbash.py b/ciphers/atbash.py index bd48f49aaaa5..6930f07861ef 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -2,39 +2,35 @@ import string + def atbash(text: str) -> str: """ Encodes or decodes text using the Atbash cipher. - + The Atbash cipher substitutes each letter with its mirror in the alphabet: A -> Z, B -> Y, C -> X, ... Z -> A (case is preserved) Non-alphabetic characters are left unchanged. - + Args: text: The input string to encode/decode - + Returns: The transformed string """ # Create translation tables for uppercase and lowercase - lowercase_map = str.maketrans( - string.ascii_lowercase, - string.ascii_lowercase[::-1] - ) - uppercase_map = str.maketrans( - string.ascii_uppercase, - string.ascii_uppercase[::-1] - ) - + lowercase_map = str.maketrans(string.ascii_lowercase, string.ascii_lowercase[::-1]) + uppercase_map = str.maketrans(string.ascii_uppercase, string.ascii_uppercase[::-1]) + # Apply both translation mappings return text.translate(lowercase_map).translate(uppercase_map) + # Example usage if __name__ == "__main__": test_string = "Hello, World! 123" encoded = atbash(test_string) decoded = atbash(encoded) - + print(f"Original: {test_string}") print(f"Encoded: {encoded}") print(f"Decoded: {decoded}") From 23772e7059478d6a20544b3e7c2705761eb14357 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 19:44:06 +0800 Subject: [PATCH 073/156] Update pascal_triangle.py --- matrix/pascal_triangle.py | 88 +++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/matrix/pascal_triangle.py b/matrix/pascal_triangle.py index 7f6555f9c8b9..a4d0d4678c86 100644 --- a/matrix/pascal_triangle.py +++ b/matrix/pascal_triangle.py @@ -1,16 +1,19 @@ """ -This implementation demonstrates how to generate the elements of a Pascal's triangle. -The element havingva row index of r and column index of c can be derivedvas follows: -triangle[r][c] = triangle[r-1][c-1]+triangle[r-1][c] +This implementation demonstrates how to generate the elements of Pascal's Triangle. +An element with row index r and column index c can be derived as: +triangle[r][c] = triangle[r-1][c-1] + triangle[r-1][c] -A Pascal's triangle is a triangular array containing binomial coefficients. +Pascal's Triangle is a triangular array containing binomial coefficients. https://en.wikipedia.org/wiki/Pascal%27s_triangle """ +from typing import List, Callable +from timeit import timeit + def print_pascal_triangle(num_rows: int) -> None: """ - Print Pascal's triangle for different number of rows + Print Pascal's triangle for the specified number of rows >>> print_pascal_triangle(5) 1 1 1 @@ -20,7 +23,7 @@ def print_pascal_triangle(num_rows: int) -> None: """ triangle = generate_pascal_triangle(num_rows) for row_idx in range(num_rows): - # Print left spaces + # Print leading spaces for _ in range(num_rows - row_idx - 1): print(end=" ") # Print row values @@ -32,9 +35,9 @@ def print_pascal_triangle(num_rows: int) -> None: print() -def generate_pascal_triangle(num_rows: int) -> list[list[int]]: +def generate_pascal_triangle(num_rows: int) -> List[List[int]]: """ - Create Pascal's triangle for different number of rows + Generate Pascal's triangle for the specified number of rows >>> generate_pascal_triangle(0) [] >>> generate_pascal_triangle(1) @@ -50,38 +53,36 @@ def generate_pascal_triangle(num_rows: int) -> list[list[int]]: >>> generate_pascal_triangle(-5) Traceback (most recent call last): ... - ValueError: The input value of 'num_rows' should be greater than or equal to 0 + ValueError: Input value 'num_rows' must be >= 0 >>> generate_pascal_triangle(7.89) Traceback (most recent call last): ... - TypeError: The input value of 'num_rows' should be 'int' + TypeError: Input value 'num_rows' must be an integer """ if not isinstance(num_rows, int): - raise TypeError("The input value of 'num_rows' should be 'int'") + raise TypeError("Input value 'num_rows' must be an integer") if num_rows == 0: return [] - elif num_rows < 0: - raise ValueError( - "The input value of 'num_rows' should be greater than or equal to 0" - ) + if num_rows < 0: + raise ValueError("Input value 'num_rows' must be >= 0") - triangle: list[list[int]] = [] + triangle: List[List[int]] = [] for current_row_idx in range(num_rows): current_row = populate_current_row(triangle, current_row_idx) triangle.append(current_row) return triangle -def populate_current_row(triangle: list[list[int]], current_row_idx: int) -> list[int]: +def populate_current_row(triangle: List[List[int]], current_row_idx: int) -> List[int]: """ >>> triangle = [[1]] >>> populate_current_row(triangle, 1) [1, 1] """ current_row = [-1] * (current_row_idx + 1) - # first and last elements of current row are equal to 1 + # First and last elements of current row are always 1 current_row[0], current_row[-1] = 1, 1 for current_col_idx in range(1, current_row_idx): calculate_current_element( @@ -89,10 +90,9 @@ def populate_current_row(triangle: list[list[int]], current_row_idx: int) -> lis ) return current_row - def calculate_current_element( - triangle: list[list[int]], - current_row: list[int], + triangle: List[List[int]], + current_row: List[int], current_row_idx: int, current_col_idx: int, ) -> None: @@ -103,22 +103,19 @@ def calculate_current_element( >>> current_row [1, 2, 1] """ - above_to_left_elt = triangle[current_row_idx - 1][current_col_idx - 1] - above_to_right_elt = triangle[current_row_idx - 1][current_col_idx] - current_row[current_col_idx] = above_to_left_elt + above_to_right_elt + above_left = triangle[current_row_idx - 1][current_col_idx - 1] + above_right = triangle[current_row_idx - 1][current_col_idx] + current_row[current_col_idx] = above_left + above_right -def generate_pascal_triangle_optimized(num_rows: int) -> list[list[int]]: +def generate_pascal_triangle_optimized(num_rows: int) -> List[List[int]]: """ - This function returns a matrix representing the corresponding pascal's triangle - according to the given input of number of rows of Pascal's triangle to be generated. - It reduces the operations done to generate a row by half - by eliminating redundant calculations. - - :param num_rows: Integer specifying the number of rows in the Pascal's triangle - :return: 2-D List (matrix) representing the Pascal's triangle - - Return the Pascal's triangle of given rows + Returns a matrix representing Pascal's triangle. + Reduces operations by half by eliminating redundant calculations. + + :param num_rows: Number of rows in the Pascal's triangle + :return: 2D list representing the Pascal's triangle + >>> generate_pascal_triangle_optimized(3) [[1], [1, 1], [1, 2, 1]] >>> generate_pascal_triangle_optimized(1) @@ -128,29 +125,27 @@ def generate_pascal_triangle_optimized(num_rows: int) -> list[list[int]]: >>> generate_pascal_triangle_optimized(-5) Traceback (most recent call last): ... - ValueError: The input value of 'num_rows' should be greater than or equal to 0 + ValueError: Input value 'num_rows' must be >= 0 >>> generate_pascal_triangle_optimized(7.89) Traceback (most recent call last): ... - TypeError: The input value of 'num_rows' should be 'int' + TypeError: Input value 'num_rows' must be an integer """ if not isinstance(num_rows, int): - raise TypeError("The input value of 'num_rows' should be 'int'") + raise TypeError("Input value 'num_rows' must be an integer") if num_rows == 0: return [] - elif num_rows < 0: - raise ValueError( - "The input value of 'num_rows' should be greater than or equal to 0" - ) + if num_rows < 0: + raise ValueError("Input value 'num_rows' must be >= 0") - result: list[list[int]] = [[1]] + result: List[List[int]] = [[1]] for row_index in range(1, num_rows): temp_row = [0] + result[-1] + [0] row_length = row_index + 1 - # Calculate the number of distinct elements in a row + # Calculate number of distinct elements in row distinct_elements = sum(divmod(row_length, 2)) row_first_half = [ temp_row[i - 1] + temp_row[i] for i in range(1, distinct_elements + 1) @@ -162,18 +157,13 @@ def generate_pascal_triangle_optimized(num_rows: int) -> list[list[int]]: return result - def benchmark() -> None: """ - Benchmark multiple functions, with three different length int values. + Benchmark functions with different input sizes """ - from collections.abc import Callable - from timeit import timeit - def benchmark_a_function(func: Callable, value: int) -> None: call = f"{func.__name__}({value})" timing = timeit(f"__main__.{call}", setup="import __main__") - # print(f"{call:38} = {func(value)} -- {timing:.4f} seconds") print(f"{call:38} -- {timing:.4f} seconds") for value in range(15): # (1, 7, 14): From adc021041356bca1767db266ff18ec7f365e4729 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 11:44:29 +0000 Subject: [PATCH 074/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- matrix/pascal_triangle.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/matrix/pascal_triangle.py b/matrix/pascal_triangle.py index a4d0d4678c86..5d0d4dc8f66f 100644 --- a/matrix/pascal_triangle.py +++ b/matrix/pascal_triangle.py @@ -90,6 +90,7 @@ def populate_current_row(triangle: List[List[int]], current_row_idx: int) -> Lis ) return current_row + def calculate_current_element( triangle: List[List[int]], current_row: List[int], @@ -112,10 +113,10 @@ def generate_pascal_triangle_optimized(num_rows: int) -> List[List[int]]: """ Returns a matrix representing Pascal's triangle. Reduces operations by half by eliminating redundant calculations. - + :param num_rows: Number of rows in the Pascal's triangle :return: 2D list representing the Pascal's triangle - + >>> generate_pascal_triangle_optimized(3) [[1], [1, 1], [1, 2, 1]] >>> generate_pascal_triangle_optimized(1) @@ -157,10 +158,12 @@ def generate_pascal_triangle_optimized(num_rows: int) -> List[List[int]]: return result + def benchmark() -> None: """ Benchmark functions with different input sizes """ + def benchmark_a_function(func: Callable, value: int) -> None: call = f"{func.__name__}({value})" timing = timeit(f"__main__.{call}", setup="import __main__") From 13d179b2cc6aeb60fce20485902993f01b521351 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 19:51:21 +0800 Subject: [PATCH 075/156] Update pascal_triangle.py --- matrix/pascal_triangle.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/matrix/pascal_triangle.py b/matrix/pascal_triangle.py index 5d0d4dc8f66f..6209d3fa7d5b 100644 --- a/matrix/pascal_triangle.py +++ b/matrix/pascal_triangle.py @@ -7,7 +7,7 @@ https://en.wikipedia.org/wiki/Pascal%27s_triangle """ -from typing import List, Callable +from collections.abc import Callable from timeit import timeit @@ -35,7 +35,7 @@ def print_pascal_triangle(num_rows: int) -> None: print() -def generate_pascal_triangle(num_rows: int) -> List[List[int]]: +def generate_pascal_triangle(num_rows: int) -> list[list[int]]: """ Generate Pascal's triangle for the specified number of rows >>> generate_pascal_triangle(0) @@ -68,14 +68,13 @@ def generate_pascal_triangle(num_rows: int) -> List[List[int]]: if num_rows < 0: raise ValueError("Input value 'num_rows' must be >= 0") - triangle: List[List[int]] = [] + triangle: list[list[int]] = [] for current_row_idx in range(num_rows): current_row = populate_current_row(triangle, current_row_idx) triangle.append(current_row) return triangle - -def populate_current_row(triangle: List[List[int]], current_row_idx: int) -> List[int]: +def populate_current_row(triangle: list[list[int]], current_row_idx: int) -> list[int]: """ >>> triangle = [[1]] >>> populate_current_row(triangle, 1) @@ -92,8 +91,8 @@ def populate_current_row(triangle: List[List[int]], current_row_idx: int) -> Lis def calculate_current_element( - triangle: List[List[int]], - current_row: List[int], + triangle: list[list[int]], + current_row: list[int], current_row_idx: int, current_col_idx: int, ) -> None: @@ -109,14 +108,14 @@ def calculate_current_element( current_row[current_col_idx] = above_left + above_right -def generate_pascal_triangle_optimized(num_rows: int) -> List[List[int]]: +def generate_pascal_triangle_optimized(num_rows: int) -> list[list[int]]: """ Returns a matrix representing Pascal's triangle. Reduces operations by half by eliminating redundant calculations. - + :param num_rows: Number of rows in the Pascal's triangle :return: 2D list representing the Pascal's triangle - + >>> generate_pascal_triangle_optimized(3) [[1], [1, 1], [1, 2, 1]] >>> generate_pascal_triangle_optimized(1) @@ -141,7 +140,7 @@ def generate_pascal_triangle_optimized(num_rows: int) -> List[List[int]]: if num_rows < 0: raise ValueError("Input value 'num_rows' must be >= 0") - result: List[List[int]] = [[1]] + result: list[list[int]] = [[1]] for row_index in range(1, num_rows): temp_row = [0] + result[-1] + [0] @@ -159,11 +158,11 @@ def generate_pascal_triangle_optimized(num_rows: int) -> List[List[int]]: return result + def benchmark() -> None: """ Benchmark functions with different input sizes """ - def benchmark_a_function(func: Callable, value: int) -> None: call = f"{func.__name__}({value})" timing = timeit(f"__main__.{call}", setup="import __main__") From 21e2c4569e523aaf672737f99f78ef1559a3bd7c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 11:51:45 +0000 Subject: [PATCH 076/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- matrix/pascal_triangle.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/matrix/pascal_triangle.py b/matrix/pascal_triangle.py index 6209d3fa7d5b..4b087525de27 100644 --- a/matrix/pascal_triangle.py +++ b/matrix/pascal_triangle.py @@ -74,6 +74,7 @@ def generate_pascal_triangle(num_rows: int) -> list[list[int]]: triangle.append(current_row) return triangle + def populate_current_row(triangle: list[list[int]], current_row_idx: int) -> list[int]: """ >>> triangle = [[1]] @@ -112,10 +113,10 @@ def generate_pascal_triangle_optimized(num_rows: int) -> list[list[int]]: """ Returns a matrix representing Pascal's triangle. Reduces operations by half by eliminating redundant calculations. - + :param num_rows: Number of rows in the Pascal's triangle :return: 2D list representing the Pascal's triangle - + >>> generate_pascal_triangle_optimized(3) [[1], [1, 1], [1, 2, 1]] >>> generate_pascal_triangle_optimized(1) @@ -158,11 +159,11 @@ def generate_pascal_triangle_optimized(num_rows: int) -> list[list[int]]: return result - def benchmark() -> None: """ Benchmark functions with different input sizes """ + def benchmark_a_function(func: Callable, value: int) -> None: call = f"{func.__name__}({value})" timing = timeit(f"__main__.{call}", setup="import __main__") From 4ad424aa9fa9313e62d65f1f15eb79961dc10137 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 29 Jun 2025 20:12:24 +0800 Subject: [PATCH 077/156] Update shuffled_shift_cipher.py --- ciphers/shuffled_shift_cipher.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index 08b2cab97c69..5304b0fa4e4e 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -42,7 +42,7 @@ def __str__(self) -> str: """ :return: passcode of the cipher object """ - return "".join(self.__passcode) + return self.__passcode def __neg_pos(self, iterlist: list[int]) -> list[int]: """ @@ -55,20 +55,19 @@ def __neg_pos(self, iterlist: list[int]) -> list[int]: for i in range(1, len(iterlist), 2): iterlist[i] *= -1 return iterlist - - def __passcode_creator(self) -> list[str]: + def __passcode_creator(self) -> str: """ Creates a random password from the selection buffer of 1. uppercase letters of the English alphabet 2. lowercase letters of the English alphabet 3. digits from 0 to 9 - :rtype: list + :rtype: str :return: a password of a random length between 10 to 20 """ choices = string.ascii_letters + string.digits password = [random.choice(choices) for _ in range(random.randint(10, 20))] - return password + return ''.join(password) def __make_key_list(self) -> list[str]: """ @@ -104,15 +103,14 @@ def __make_key_list(self) -> list[str]: temp_list: list[str] = [] # algorithm for creating a new shuffled list, keys_l, out of key_list_options - for i in key_list_options: - temp_list.extend(i) + for char in key_list_options: + temp_list.append(char) # checking breakpoints at which to pivot temporary sublist and add it into # keys_l - if i in breakpoints or i == key_list_options[-1]: + if char in breakpoints or char == key_list_options[-1]: keys_l.extend(temp_list[::-1]) temp_list.clear() - # returning a shuffled keys_l to prevent brute force guessing of shift key return keys_l @@ -135,13 +133,14 @@ def decrypt(self, encoded_message: str) -> str: """ decoded_message = "" + key_len = len(self.__key_list) # decoding shift like Caesar cipher algorithm implementing negative shift or # reverse shift or left shift - for i in encoded_message: - position = self.__key_list.index(i) + for char in encoded_message: + position = self.__key_list.index(char) decoded_message += self.__key_list[ - (position - self.__shift_key) % -len(self.__key_list) + (position - self.__shift_key) % key_len ] return decoded_message @@ -157,13 +156,14 @@ def encrypt(self, plaintext: str) -> str: """ encoded_message = "" + key_len = len(self.__key_list) # encoding shift like Caesar cipher algorithm implementing positive shift or # forward shift or right shift - for i in plaintext: - position = self.__key_list.index(i) + for char in plaintext: + position = self.__key_list.index(char) encoded_message += self.__key_list[ - (position + self.__shift_key) % len(self.__key_list) + (position + self.__shift_key) % key_len ] return encoded_message From c901985cc7beaa6919ceba2f921f28ef4d72ae44 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 12:12:47 +0000 Subject: [PATCH 078/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/shuffled_shift_cipher.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index 5304b0fa4e4e..09c1c65d0117 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -55,6 +55,7 @@ def __neg_pos(self, iterlist: list[int]) -> list[int]: for i in range(1, len(iterlist), 2): iterlist[i] *= -1 return iterlist + def __passcode_creator(self) -> str: """ Creates a random password from the selection buffer of @@ -67,7 +68,7 @@ def __passcode_creator(self) -> str: """ choices = string.ascii_letters + string.digits password = [random.choice(choices) for _ in range(random.randint(10, 20))] - return ''.join(password) + return "".join(password) def __make_key_list(self) -> list[str]: """ @@ -139,9 +140,7 @@ def decrypt(self, encoded_message: str) -> str: # reverse shift or left shift for char in encoded_message: position = self.__key_list.index(char) - decoded_message += self.__key_list[ - (position - self.__shift_key) % key_len - ] + decoded_message += self.__key_list[(position - self.__shift_key) % key_len] return decoded_message @@ -162,9 +161,7 @@ def encrypt(self, plaintext: str) -> str: # forward shift or right shift for char in plaintext: position = self.__key_list.index(char) - encoded_message += self.__key_list[ - (position + self.__shift_key) % key_len - ] + encoded_message += self.__key_list[(position + self.__shift_key) % key_len] return encoded_message From 7bbfdca178ca1c37d8e9a478891bf104bdb82412 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 22:53:19 +0800 Subject: [PATCH 079/156] Update diff_views_of_binary_tree.py From 9ab3e07e555710957bd1d4035dbd230f6d707f50 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 22:58:49 +0800 Subject: [PATCH 080/156] Update binary_search_tree.py --- data_structures/binary_tree/binary_search_tree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 3f214d0113a4..c0c41d2c2445 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -166,7 +166,6 @@ def empty(self) -> bool: False """ return not self.root - def __insert(self, value) -> None: """ Insert a new node in Binary Search Tree with value label From fef70fe909cffb4312864ebce96b5a3e61850496 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 14:59:59 +0000 Subject: [PATCH 081/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/binary_search_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index c0c41d2c2445..3f214d0113a4 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -166,6 +166,7 @@ def empty(self) -> bool: False """ return not self.root + def __insert(self, value) -> None: """ Insert a new node in Binary Search Tree with value label From 33b0846c4e16e3680a6b35306f99689133a78b46 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:10:29 +0800 Subject: [PATCH 082/156] Delete data_structures/binary_tree/red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 716 ------------------ 1 file changed, 716 deletions(-) delete mode 100644 data_structures/binary_tree/red_black_tree.py diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py deleted file mode 100644 index 752db1e7026c..000000000000 --- a/data_structures/binary_tree/red_black_tree.py +++ /dev/null @@ -1,716 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterator - - -class RedBlackTree: - """ - A Red-Black tree, which is a self-balancing BST (binary search - tree). - This tree has similar performance to AVL trees, but the balancing is - less strict, so it will perform faster for writing/deleting nodes - and slower for reading in the average case, though, because they're - both balanced binary search trees, both will get the same asymptotic - performance. - To read more about them, https://en.wikipedia.org/wiki/Red-black_tree - Unless otherwise specified, all asymptotic runtimes are specified in - terms of the size of the tree. - """ - - def __init__( - self, - label: int | None = None, - color: int = 0, - parent: RedBlackTree | None = None, - left: RedBlackTree | None = None, - right: RedBlackTree | None = None, - ) -> None: - """Initialize a new Red-Black Tree node with the given values: - label: The value associated with this node - color: 0 if black, 1 if red - parent: The parent to this node - left: This node's left child - right: This node's right child - """ - self.label = label - self.parent = parent - self.left = left - self.right = right - self.color = color - - # Here are functions which are specific to red-black trees - - def rotate_left(self) -> RedBlackTree: - """Rotate the subtree rooted at this node to the left and - returns the new root to this subtree. - Performing one rotation can be done in O(1). - """ - parent = self.parent - right = self.right - if right is None: - return self - self.right = right.left - if self.right: - self.right.parent = self - self.parent = right - right.left = self - if parent is not None: - if parent.left == self: - parent.left = right - else: - parent.right = right - right.parent = parent - return right - - def rotate_right(self) -> RedBlackTree: - """Rotate the subtree rooted at this node to the right and - returns the new root to this subtree. - Performing one rotation can be done in O(1). - """ - if self.left is None: - return self - parent = self.parent - left = self.left - self.left = left.right - if self.left: - self.left.parent = self - self.parent = left - left.right = self - if parent is not None: - if parent.right is self: - parent.right = left - else: - parent.left = left - left.parent = parent - return left - - def insert(self, label: int) -> RedBlackTree: - """Inserts label into the subtree rooted at self, performs any - rotations necessary to maintain balance, and then returns the - new root to this subtree (likely self). - This is guaranteed to run in O(log(n)) time. - """ - if self.label is None: - # Only possible with an empty tree - self.label = label - return self - if self.label == label: - return self - elif self.label > label: - if self.left: - self.left.insert(label) - else: - self.left = RedBlackTree(label, 1, self) - self.left._insert_repair() - elif self.right: - self.right.insert(label) - else: - self.right = RedBlackTree(label, 1, self) - self.right._insert_repair() - return self.parent or self - - def _insert_repair(self) -> None: - """Repair the coloring from inserting into a tree.""" - if self.parent is None: - # This node is the root, so it just needs to be black - self.color = 0 - elif color(self.parent) == 0: - # If the parent is black, then it just needs to be red - self.color = 1 - else: - uncle = self.parent.sibling - if color(uncle) == 0: - if self.is_left() and self.parent.is_right(): - self.parent.rotate_right() - if self.right: - self.right._insert_repair() - elif self.is_right() and self.parent.is_left(): - self.parent.rotate_left() - if self.left: - self.left._insert_repair() - elif self.is_left(): - if self.grandparent: - self.grandparent.rotate_right() - self.parent.color = 0 - if self.parent.right: - self.parent.right.color = 1 - else: - if self.grandparent: - self.grandparent.rotate_left() - self.parent.color = 0 - if self.parent.left: - self.parent.left.color = 1 - else: - self.parent.color = 0 - if uncle and self.grandparent: - uncle.color = 0 - self.grandparent.color = 1 - self.grandparent._insert_repair() - - def remove(self, label: int) -> RedBlackTree: - """Remove label from this tree.""" - if self.label == label: - if self.left and self.right: - # It's easier to balance a node with at most one child, - # so we replace this node with the greatest one less than - # it and remove that. - value = self.left.get_max() - if value is not None: - self.label = value - self.left.remove(value) - else: - # This node has at most one non-None child, so we don't - # need to replace - child = self.left or self.right - if self.color == 1: - # This node is red, and its child is black - # The only way this happens to a node with one child - # is if both children are None leaves. - # We can just remove this node and call it a day. - if self.parent: - if self.is_left(): - self.parent.left = None - else: - self.parent.right = None - # The node is black - elif child is None: - # This node and its child are black - if self.parent is None: - # The tree is now empty - return RedBlackTree(None) - else: - self._remove_repair() - if self.is_left(): - self.parent.left = None - else: - self.parent.right = None - self.parent = None - else: - # This node is black and its child is red - # Move the child node here and make it black - self.label = child.label - self.left = child.left - self.right = child.right - if self.left: - self.left.parent = self - if self.right: - self.right.parent = self - elif self.label is not None and self.label > label: - if self.left: - self.left.remove(label) - elif self.right: - self.right.remove(label) - return self.parent or self - - def _remove_repair(self) -> None: - """Repair the coloring of the tree that may have been messed up.""" - if ( - self.parent is None - or self.sibling is None - or self.parent.sibling is None - or self.grandparent is None - ): - return - if color(self.sibling) == 1: - self.sibling.color = 0 - self.parent.color = 1 - if self.is_left(): - self.parent.rotate_left() - else: - self.parent.rotate_right() - if ( - color(self.parent) == 0 - and color(self.sibling) == 0 - and color(self.sibling.left) == 0 - and color(self.sibling.right) == 0 - ): - self.sibling.color = 1 - self.parent._remove_repair() - return - if ( - color(self.parent) == 1 - and color(self.sibling) == 0 - and color(self.sibling.left) == 0 - and color(self.sibling.right) == 0 - ): - self.sibling.color = 1 - self.parent.color = 0 - return - if ( - self.is_left() - and color(self.sibling) == 0 - and color(self.sibling.right) == 0 - and color(self.sibling.left) == 1 - ): - self.sibling.rotate_right() - self.sibling.color = 0 - if self.sibling.right: - self.sibling.right.color = 1 - if ( - self.is_right() - and color(self.sibling) == 0 - and color(self.sibling.right) == 1 - and color(self.sibling.left) == 0 - ): - self.sibling.rotate_left() - self.sibling.color = 0 - if self.sibling.left: - self.sibling.left.color = 1 - if ( - self.is_left() - and color(self.sibling) == 0 - and color(self.sibling.right) == 1 - ): - self.parent.rotate_left() - self.grandparent.color = self.parent.color - self.parent.color = 0 - self.parent.sibling.color = 0 - if ( - self.is_right() - and color(self.sibling) == 0 - and color(self.sibling.left) == 1 - ): - self.parent.rotate_right() - self.grandparent.color = self.parent.color - self.parent.color = 0 - self.parent.sibling.color = 0 - - def check_color_properties(self) -> bool: - """Check the coloring of the tree, and return True iff the tree - is colored in a way which matches these five properties: - (wording stolen from wikipedia article) - 1. Each node is either red or black. - 2. The root node is black. - 3. All leaves are black. - 4. If a node is red, then both its children are black. - 5. Every path from any node to all of its descendent NIL nodes - has the same number of black nodes. - This function runs in O(n) time, because properties 4 and 5 take - that long to check. - """ - # I assume property 1 to hold because there is nothing that can - # make the color be anything other than 0 or 1. - # Property 2 - if self.color: - # The root was red - print("Property 2") - return False - # Property 3 does not need to be checked, because None is assumed - # to be black and is all the leaves. - # Property 4 - if not self.check_coloring(): - print("Property 4") - return False - # Property 5 - if self.black_height() is None: - print("Property 5") - return False - # All properties were met - return True - - def check_coloring(self) -> bool: - """A helper function to recursively check Property 4 of a - Red-Black Tree. See check_color_properties for more info. - """ - if self.color == 1 and 1 in (color(self.left), color(self.right)): - return False - if self.left and not self.left.check_coloring(): - return False - return not (self.right and not self.right.check_coloring()) - - def black_height(self) -> int | None: - """Returns the number of black nodes from this node to the - leaves of the tree, or None if there isn't one such value (the - tree is color incorrectly). - """ - if self is None or self.left is None or self.right is None: - # If we're already at a leaf, there is no path - return 1 - left = RedBlackTree.black_height(self.left) - right = RedBlackTree.black_height(self.right) - if left is None or right is None: - # There are issues with coloring below children nodes - return None - if left != right: - # The two children have unequal depths - return None - # Return the black depth of children, plus one if this node is - # black - return left + (1 - self.color) - - # Here are functions which are general to all binary search trees - - def __contains__(self, label: int) -> bool: - """Search through the tree for label, returning True iff it is - found somewhere in the tree. - Guaranteed to run in O(log(n)) time. - """ - return self.search(label) is not None - - def search(self, label: int) -> RedBlackTree | None: - """Search through the tree for label, returning its node if - it's found, and None otherwise. - This method is guaranteed to run in O(log(n)) time. - """ - if self.label == label: - return self - elif self.label is not None and label > self.label: - if self.right is None: - return None - else: - return self.right.search(label) - elif self.left is None: - return None - else: - return self.left.search(label) - - def floor(self, label: int) -> int | None: - """Returns the largest element in this tree which is at most label. - This method is guaranteed to run in O(log(n)) time.""" - if self.label == label: - return self.label - elif self.label is not None and self.label > label: - if self.left: - return self.left.floor(label) - else: - return None - else: - if self.right: - attempt = self.right.floor(label) - if attempt is not None: - return attempt - return self.label - - def ceil(self, label: int) -> int | None: - """Returns the smallest element in this tree which is at least label. - This method is guaranteed to run in O(log(n)) time. - """ - if self.label == label: - return self.label - elif self.label is not None and self.label < label: - if self.right: - return self.right.ceil(label) - else: - return None - else: - if self.left: - attempt = self.left.ceil(label) - if attempt is not None: - return attempt - return self.label - - def get_max(self) -> int | None: - """Returns the largest element in this tree. - This method is guaranteed to run in O(log(n)) time. - """ - if self.right: - # Go as far right as possible - return self.right.get_max() - else: - return self.label - - def get_min(self) -> int | None: - """Returns the smallest element in this tree. - This method is guaranteed to run in O(log(n)) time. - """ - if self.left: - # Go as far left as possible - return self.left.get_min() - else: - return self.label - - @property - def grandparent(self) -> RedBlackTree | None: - """Get the current node's grandparent, or None if it doesn't exist.""" - if self.parent is None: - return None - else: - return self.parent.parent - - @property - def sibling(self) -> RedBlackTree | None: - """Get the current node's sibling, or None if it doesn't exist.""" - if self.parent is None: - return None - elif self.parent.left is self: - return self.parent.right - else: - return self.parent.left - - def is_left(self) -> bool: - """Returns true iff this node is the left child of its parent.""" - if self.parent is None: - return False - return self.parent.left is self - - def is_right(self) -> bool: - """Returns true iff this node is the right child of its parent.""" - if self.parent is None: - return False - return self.parent.right is self - - def __bool__(self) -> bool: - return True - - def __len__(self) -> int: - """ - Return the number of nodes in this tree. - """ - ln = 1 - if self.left: - ln += len(self.left) - if self.right: - ln += len(self.right) - return ln - - def preorder_traverse(self) -> Iterator[int | None]: - yield self.label - if self.left: - yield from self.left.preorder_traverse() - if self.right: - yield from self.right.preorder_traverse() - - def inorder_traverse(self) -> Iterator[int | None]: - if self.left: - yield from self.left.inorder_traverse() - yield self.label - if self.right: - yield from self.right.inorder_traverse() - - def postorder_traverse(self) -> Iterator[int | None]: - if self.left: - yield from self.left.postorder_traverse() - if self.right: - yield from self.right.postorder_traverse() - yield self.label - - def __repr__(self) -> str: - from pprint import pformat - - if self.left is None and self.right is None: - return f"'{self.label} {(self.color and 'red') or 'blk'}'" - return pformat( - { - f"{self.label} {(self.color and 'red') or 'blk'}": ( - self.left, - self.right, - ) - }, - indent=1, - ) - - def __eq__(self, other: object) -> bool: - """Test if two trees are equal.""" - if not isinstance(other, RedBlackTree): - return NotImplemented - if self.label == other.label: - return self.left == other.left and self.right == other.right - else: - return False - - -def color(node: RedBlackTree | None) -> int: - """Returns the color of a node, allowing for None leaves.""" - if node is None: - return 0 - else: - return node.color - - -""" -Code for testing the various -functions of the red-black tree. -""" - - -def test_rotations() -> bool: - """Test that the rotate_left and rotate_right functions work.""" - # Make a tree to test on - tree = RedBlackTree(0) - tree.left = RedBlackTree(-10, parent=tree) - tree.right = RedBlackTree(10, parent=tree) - tree.left.left = RedBlackTree(-20, parent=tree.left) - tree.left.right = RedBlackTree(-5, parent=tree.left) - tree.right.left = RedBlackTree(5, parent=tree.right) - tree.right.right = RedBlackTree(20, parent=tree.right) - # Make the right rotation - left_rot = RedBlackTree(10) - left_rot.left = RedBlackTree(0, parent=left_rot) - left_rot.left.left = RedBlackTree(-10, parent=left_rot.left) - left_rot.left.right = RedBlackTree(5, parent=left_rot.left) - left_rot.left.left.left = RedBlackTree(-20, parent=left_rot.left.left) - left_rot.left.left.right = RedBlackTree(-5, parent=left_rot.left.left) - left_rot.right = RedBlackTree(20, parent=left_rot) - tree = tree.rotate_left() - if tree != left_rot: - return False - tree = tree.rotate_right() - tree = tree.rotate_right() - # Make the left rotation - right_rot = RedBlackTree(-10) - right_rot.left = RedBlackTree(-20, parent=right_rot) - right_rot.right = RedBlackTree(0, parent=right_rot) - right_rot.right.left = RedBlackTree(-5, parent=right_rot.right) - right_rot.right.right = RedBlackTree(10, parent=right_rot.right) - right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.right) - right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right) - return tree == right_rot - - -def test_insertion_speed() -> bool: - """Test that the tree balances inserts to O(log(n)) by doing a lot - of them. - """ - tree = RedBlackTree(-1) - for i in range(300000): - tree = tree.insert(i) - return True - - -def test_insert() -> bool: - """Test the insert() method of the tree correctly balances, colors, - and inserts. - """ - tree = RedBlackTree(0) - tree.insert(8) - tree.insert(-8) - tree.insert(4) - tree.insert(12) - tree.insert(10) - tree.insert(11) - ans = RedBlackTree(0, 0) - ans.left = RedBlackTree(-8, 0, ans) - ans.right = RedBlackTree(8, 1, ans) - ans.right.left = RedBlackTree(4, 0, ans.right) - ans.right.right = RedBlackTree(11, 0, ans.right) - ans.right.right.left = RedBlackTree(10, 1, ans.right.right) - ans.right.right.right = RedBlackTree(12, 1, ans.right.right) - return tree == ans - - -def test_insert_and_search() -> bool: - """Tests searching through the tree for values.""" - tree = RedBlackTree(0) - tree.insert(8) - tree.insert(-8) - tree.insert(4) - tree.insert(12) - tree.insert(10) - tree.insert(11) - if any(i in tree for i in (5, -6, -10, 13)): - # Found something not in there - return False - # Find all these things in there - return all(i in tree for i in (11, 12, -8, 0)) - - -def test_insert_delete() -> bool: - """Test the insert() and delete() method of the tree, verifying the - insertion and removal of elements, and the balancing of the tree. - """ - tree = RedBlackTree(0) - tree = tree.insert(-12) - tree = tree.insert(8) - tree = tree.insert(-8) - tree = tree.insert(15) - tree = tree.insert(4) - tree = tree.insert(12) - tree = tree.insert(10) - tree = tree.insert(9) - tree = tree.insert(11) - tree = tree.remove(15) - tree = tree.remove(-12) - tree = tree.remove(9) - if not tree.check_color_properties(): - return False - return list(tree.inorder_traverse()) == [-8, 0, 4, 8, 10, 11, 12] - - -def test_floor_ceil() -> bool: - """Tests the floor and ceiling functions in the tree.""" - tree = RedBlackTree(0) - tree.insert(-16) - tree.insert(16) - tree.insert(8) - tree.insert(24) - tree.insert(20) - tree.insert(22) - tuples = [(-20, None, -16), (-10, -16, 0), (8, 8, 8), (50, 24, None)] - for val, floor, ceil in tuples: - if tree.floor(val) != floor or tree.ceil(val) != ceil: - return False - return True - - -def test_min_max() -> bool: - """Tests the min and max functions in the tree.""" - tree = RedBlackTree(0) - tree.insert(-16) - tree.insert(16) - tree.insert(8) - tree.insert(24) - tree.insert(20) - tree.insert(22) - return not (tree.get_max() != 22 or tree.get_min() != -16) - - -def test_tree_traversal() -> bool: - """Tests the three different tree traversal functions.""" - tree = RedBlackTree(0) - tree = tree.insert(-16) - tree.insert(16) - tree.insert(8) - tree.insert(24) - tree.insert(20) - tree.insert(22) - if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: - return False - if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: - return False - return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0] - - -def test_tree_chaining() -> bool: - """Tests the three different tree chaining functions.""" - tree = RedBlackTree(0) - tree = tree.insert(-16).insert(16).insert(8).insert(24).insert(20).insert(22) - if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: - return False - if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: - return False - return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0] - - -def print_results(msg: str, passes: bool) -> None: - print(str(msg), "works!" if passes else "doesn't work :(") - - -def pytests() -> None: - assert test_rotations() - assert test_insert() - assert test_insert_and_search() - assert test_insert_delete() - assert test_floor_ceil() - assert test_tree_traversal() - assert test_tree_chaining() - - -def main() -> None: - """ - >>> pytests() - """ - print_results("Rotating right and left", test_rotations()) - print_results("Inserting", test_insert()) - print_results("Searching", test_insert_and_search()) - print_results("Deleting", test_insert_delete()) - print_results("Floor and ceil", test_floor_ceil()) - print_results("Tree traversal", test_tree_traversal()) - print_results("Tree traversal", test_tree_chaining()) - print("Testing tree balancing...") - print("This should only be a few seconds.") - test_insertion_speed() - print("Done!") - - -if __name__ == "__main__": - main() From bac8021544eb4f6d5904fb0e08188bf3b740de66 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:11:50 +0800 Subject: [PATCH 083/156] Add red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 862 ++++++++++++++++++ 1 file changed, 862 insertions(+) create mode 100644 data_structures/binary_tree/red_black_tree.py diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py new file mode 100644 index 000000000000..cf372c62ff32 --- /dev/null +++ b/data_structures/binary_tree/red_black_tree.py @@ -0,0 +1,862 @@ +from __future__ import annotations + +from collections.abc import Iterator +from pprint import pformat + +class RedBlackTree: + """ + A Red-Black tree, which is a self-balancing BST (binary search + tree). + + Examples: + >>> tree = RedBlackTree(0) + >>> tree = tree.insert(8).insert(-8).insert(4).insert(12) + >>> tree.check_color_properties() + True + >>> list(tree.inorder_traverse()) + [-8, 0, 4, 8, 12] + >>> tree.search(4).label + 4 + >>> tree.floor(5) + 4 + >>> tree.ceil(5) + 8 + >>> tree.get_min() + -8 + >>> tree.get_max() + 12 + >>> tree = tree.remove(4) + >>> 4 in tree + False + """ + + def __init__( + self, + label: int | None = None, + color: int = 0, + parent: RedBlackTree | None = None, + left: RedBlackTree | None = None, + right: RedBlackTree | None = None, + ) -> None: + """Initialize a new Red-Black Tree node. + + Args: + label: The value associated with this node + color: 0 if black, 1 if red + parent: The parent to this node + left: This node's left child + right: This node's right child + + Examples: + >>> node = RedBlackTree(5) + >>> node.label + 5 + >>> node.color + 0 + """ + self.label = label + self.parent = parent + self.left = left + self.right = right + self.color = color + + def rotate_left(self) -> RedBlackTree: + """Rotate the subtree rooted at this node to the left. + + Returns: + The new root of the subtree + + Examples: + >>> root = RedBlackTree(2) + >>> root.right = RedBlackTree(4) + >>> root.right.left = RedBlackTree(3) + >>> new_root = root.rotate_left() + >>> new_root.label + 4 + >>> new_root.left.label + 2 + >>> new_root.left.right.label + 3 + """ + parent = self.parent + right = self.right + if right is None: + return self + self.right = right.left + if self.right: + self.right.parent = self + self.parent = right + right.left = self + if parent is not None: + if parent.left == self: + parent.left = right + else: + parent.right = right + right.parent = parent + return right + + def rotate_right(self) -> RedBlackTree: + """Rotate the subtree rooted at this node to the right. + + Returns: + The new root of the subtree + + Examples: + >>> root = RedBlackTree(4) + >>> root.left = RedBlackTree(2) + >>> root.left.right = RedBlackTree(3) + >>> new_root = root.rotate_right() + >>> new_root.label + 2 + >>> new_root.right.label + 4 + >>> new_root.right.left.label + 3 + """ + if self.left is None: + return self + parent = self.parent + left = self.left + self.left = left.right + if self.left: + self.left.parent = self + self.parent = left + left.right = self + if parent is not None: + if parent.right is self: + parent.right = left + else: + parent.left = left + left.parent = parent + return left + + def insert(self, label: int) -> RedBlackTree: + """Insert a label into the tree. + + Args: + label: The value to insert + + Returns: + The root of the tree + + Examples: + >>> tree = RedBlackTree() + >>> tree = tree.insert(5).insert(3).insert(7) + >>> list(tree.inorder_traverse()) + [3, 5, 7] + >>> tree.check_color_properties() + True + """ + if self.label is None: + self.label = label + return self + if self.label == label: + return self + elif self.label > label: + if self.left: + self.left.insert(label) + else: + self.left = RedBlackTree(label, 1, self) + self.left._insert_repair() + elif self.right: + self.right.insert(label) + else: + self.right = RedBlackTree(label, 1, self) + self.right._insert_repair() + return self.parent or self + + def _insert_repair(self) -> None: + """Repair the coloring after insertion.""" + if self.parent is None: + # This node is the root, so it just needs to be black + self.color = 0 + elif color(self.parent) == 0: + # If the parent is black, then it just needs to be red + self.color = 1 + else: + uncle = self.parent.sibling + if color(uncle) == 0: + if self.is_left() and self.parent.is_right(): + self.parent.rotate_right() + if self.right: + self.right._insert_repair() + elif self.is_right() and self.parent.is_left(): + self.parent.rotate_left() + if self.left: + self.left._insert_repair() + elif self.is_left(): + if self.grandparent: + self.grandparent.rotate_right() + self.parent.color = 0 + if self.parent.right: + self.parent.right.color = 1 + else: + if self.grandparent: + self.grandparent.rotate_left() + self.parent.color = 0 + if self.parent.left: + self.parent.left.color = 1 + else: + self.parent.color = 0 + if uncle and self.grandparent: + uncle.color = 0 + self.grandparent.color = 1 + self.grandparent._insert_repair() + + def remove(self, label: int) -> RedBlackTree: + """Remove a label from the tree. + + Args: + label: The value to remove + + Returns: + The root of the tree + + Examples: + >>> tree = RedBlackTree(5) + >>> tree = tree.insert(3).insert(7) + >>> tree = tree.remove(3) + >>> 3 in tree + False + >>> tree.check_color_properties() + True + """ + if self.label == label: + if self.left and self.right: + value = self.left.get_max() + if value is not None: + self.label = value + self.left.remove(value) + else: + child = self.left or self.right + if self.color == 1: + if self.parent: + if self.is_left(): + self.parent.left = None + else: + self.parent.right = None + elif child is None: + if self.parent is None: + return RedBlackTree(None) + else: + self._remove_repair() + if self.is_left(): + self.parent.left = None + else: + self.parent.right = None + self.parent = None + else: + self.label = child.label + self.left = child.left + self.right = child.right + if self.left: + self.left.parent = self + if self.right: + self.right.parent = self + elif self.label is not None and self.label > label: + if self.left: + self.left.remove(label) + elif self.right: + self.right.remove(label) + return self.parent or self + + def _remove_repair(self) -> None: + """Repair the coloring after removal.""" + if ( + self.parent is None + or self.sibling is None + or self.parent.sibling is None + or self.grandparent is None + ): + return + if color(self.sibling) == 1: + self.sibling.color = 0 + self.parent.color = 1 + if self.is_left(): + self.parent.rotate_left() + else: + self.parent.rotate_right() + if ( + color(self.parent) == 0 + and color(self.sibling) == 0 + and color(self.sibling.left) == 0 + and color(self.sibling.right) == 0 + ): + self.sibling.color = 1 + self.parent._remove_repair() + return + if ( + color(self.parent) == 1 + and color(self.sibling) == 0 + and color(self.sibling.left) == 0 + and color(self.sibling.right) == 0 + ): + self.sibling.color = 1 + self.parent.color = 0 + return + if ( + self.is_left() + and color(self.sibling) == 0 + and color(self.sibling.right) == 0 + and color(self.sibling.left) == 1 + ): + self.sibling.rotate_right() + self.sibling.color = 0 + if self.sibling.right: + self.sibling.right.color = 1 + if ( + self.is_right() + and color(self.sibling) == 0 + and color(self.sibling.right) == 1 + and color(self.sibling.left) == 0 + ): + self.sibling.rotate_left() + self.sibling.color = 0 + if self.sibling.left: + self.sibling.left.color = 1 + if ( + self.is_left() + and color(self.sibling) == 0 + and color(self.sibling.right) == 1 + ): + self.parent.rotate_left() + self.grandparent.color = self.parent.color + self.parent.color = 0 + self.parent.sibling.color = 0 + if ( + self.is_right() + and color(self.sibling) == 0 + and color(self.sibling.left) == 1 + ): + self.parent.rotate_right() + self.grandparent.color = self.parent.color + self.parent.color = 0 + self.parent.sibling.color = 0 + + def check_color_properties(self) -> bool: + """Check if the tree satisfies all Red-Black tree properties. + + Returns: + True if all properties are satisfied, False otherwise + + Examples: + >>> tree = RedBlackTree(0) + >>> tree = tree.insert(1).insert(-1) + >>> tree.check_color_properties() + True + """ + # Property 2 + if self.color: + return False + # Property 4 + if not self.check_coloring(): + return False + # Property 5 + if self.black_height() is None: + return False + return True + + def check_coloring(self) -> bool: + """Check if the tree satisfies Red-Black property 4.""" + if self.color == 1 and 1 in (color(self.left), color(self.right)): + return False + if self.left and not self.left.check_coloring(): + return False + return not (self.right and not self.right.check_coloring()) + + def black_height(self) -> int | None: + """Calculate the black height of the tree.""" + if self is None or self.left is None or self.right is None: + return 1 + left = RedBlackTree.black_height(self.left) + right = RedBlackTree.black_height(self.right) + if left is None or right is None: + return None + if left != right: + return None + return left + (1 - self.color) + + def __contains__(self, label: int) -> bool: + """Check if the tree contains a label. + + Args: + label: The value to check + + Returns: + True if the label is in the tree, False otherwise + + Examples: + >>> tree = RedBlackTree(5) + >>> tree = tree.insert(3) + >>> 3 in tree + True + >>> 4 in tree + False + """ + return self.search(label) is not None + + def search(self, label: int) -> RedBlackTree | None: + """Search for a label in the tree. + + Args: + label: The value to search for + + Returns: + The node containing the label, or None if not found + + Examples: + >>> tree = RedBlackTree(5) + >>> node = tree.search(5) + >>> node.label + 5 + >>> tree.search(10) is None + True + """ + if self.label == label: + return self + elif self.label is not None and label > self.label: + if self.right is None: + return None + else: + return self.right.search(label) + elif self.left is None: + return None + else: + return self.left.search(label) + + def floor(self, label: int) -> int | None: + """Find the largest element <= label. + + Args: + label: The value to find the floor of + + Returns: + The floor value, or None if no such element exists + + Examples: + >>> tree = RedBlackTree(5) + >>> tree = tree.insert(3).insert(7) + >>> tree.floor(6) + 5 + >>> tree.floor(2) is None + True + """ + if self.label == label: + return self.label + elif self.label is not None and self.label > label: + if self.left: + return self.left.floor(label) + else: + return None + else: + if self.right: + attempt = self.right.floor(label) + if attempt is not None: + return attempt + return self.label + + def ceil(self, label: int) -> int | None: + """Find the smallest element >= label. + + Args: + label: The value to find the ceil of + + Returns: + The ceil value, or None if no such element exists + + Examples: + >>> tree = RedBlackTree(5) + >>> tree = tree.insert(3).insert(7) + >>> tree.ceil(6) + 7 + >>> tree.ceil(8) is None + True + """ + if self.label == label: + return self.label + elif self.label is not None and self.label < label: + if self.right: + return self.right.ceil(label) + else: + return None + else: + if self.left: + attempt = self.left.ceil(label) + if attempt is not None: + return attempt + return self.label + + def get_max(self) -> int | None: + """Get the maximum element in the tree. + + Returns: + The maximum value, or None if the tree is empty + + Examples: + >>> tree = RedBlackTree(5) + >>> tree = tree.insert(3).insert(7) + >>> tree.get_max() + 7 + """ + if self.right: + return self.right.get_max() + else: + return self.label + + def get_min(self) -> int | None: + """Get the minimum element in the tree. + + Returns: + The minimum value, or None if the tree is empty + + Examples: + >>> tree = RedBlackTree(5) + >>> tree = tree.insert(3).insert(7) + >>> tree.get_min() + 3 + """ + if self.left: + return self.left.get_min() + else: + return self.label + + @property + def grandparent(self) -> RedBlackTree | None: + """Get the grandparent of this node.""" + if self.parent is None: + return None + else: + return self.parent.parent + + @property + def sibling(self) -> RedBlackTree | None: + """Get the sibling of this node.""" + if self.parent is None: + return None + elif self.parent.left is self: + return self.parent.right + else: + return self.parent.left + + def is_left(self) -> bool: + """Check if this node is the left child of its parent.""" + if self.parent is None: + return False + return self.parent.left is self + + def is_right(self) -> bool: + """Check if this node is the right child of its parent.""" + if self.parent is None: + return False + return self.parent.right is self + + def __bool__(self) -> bool: + """Return True if the tree is not empty.""" + return True + + def __len__(self) -> int: + """Return the number of nodes in the tree. + + Examples: + >>> tree = RedBlackTree(5) + >>> tree = tree.insert(3).insert(7) + >>> len(tree) + 3 + """ + ln = 1 + if self.left: + ln += len(self.left) + if self.right: + ln += len(self.right) + return ln + + def preorder_traverse(self) -> Iterator[int | None]: + """Traverse the tree in pre-order. + + Yields: + The values in pre-order + + Examples: + >>> tree = RedBlackTree(2) + >>> tree.left = RedBlackTree(1) + >>> tree.right = RedBlackTree(3) + >>> list(tree.preorder_traverse()) + [2, 1, 3] + """ + yield self.label + if self.left: + yield from self.left.preorder_traverse() + if self.right: + yield from self.right.preorder_traverse() + + def inorder_traverse(self) -> Iterator[int | None]: + """Traverse the tree in in-order. + + Yields: + The values in in-order + + Examples: + >>> tree = RedBlackTree(2) + >>> tree.left = RedBlackTree(1) + >>> tree.right = RedBlackTree(3) + >>> list(tree.inorder_traverse()) + [1, 2, 3] + """ + if self.left: + yield from self.left.inorder_traverse() + yield self.label + if self.right: + yield from self.right.inorder_traverse() + + def postorder_traverse(self) -> Iterator[int | None]: + """Traverse the tree in post-order. + + Yields: + The values in post-order + + Examples: + >>> tree = RedBlackTree(2) + >>> tree.left = RedBlackTree(1) + >>> tree.right = RedBlackTree(3) + >>> list(tree.postorder_traverse()) + [1, 3, 2] + """ + if self.left: + yield from self.left.postorder_traverse() + if self.right: + yield from self.right.postorder_traverse() + yield self.label + + def __repr__(self) -> str: + """Return a string representation of the tree.""" + if self.left is None and self.right is None: + return f"'{self.label} {(self.color and 'red') or 'blk'}'" + return pformat( + { + f"{self.label} {(self.color and 'red') or 'blk'}": ( + self.left, + self.right, + ) + }, + indent=1, + ) + + def __eq__(self, other: object) -> bool: + """Test if two trees are equal.""" + if not isinstance(other, RedBlackTree): + return NotImplemented + if self.label == other.label: + return self.left == other.left and self.right == other.right + else: + return False + + def __hash__(self): + """Return a hash value for the node.""" + return hash((self.label, self.color)) + + +def color(node: RedBlackTree | None) -> int: + """Returns the color of a node, allowing for None leaves.""" + if node is None: + return 0 + else: + return node.color + + +""" +Code for testing the various +functions of the red-black tree. +""" + + +def test_rotations() -> bool: + """Test that the rotate_left and rotate_right functions work.""" + tree = RedBlackTree(0) + tree.left = RedBlackTree(-10, parent=tree) + tree.right = RedBlackTree(10, parent=tree) + tree.left.left = RedBlackTree(-20, parent=tree.left) + tree.left.right = RedBlackTree(-5, parent=tree.left) + tree.right.left = RedBlackTree(5, parent=tree.right) + tree.right.right = RedBlackTree(20, parent=tree.right) + left_rot = RedBlackTree(10) + left_rot.left = RedBlackTree(0, parent=left_rot) + left_rot.left.left = RedBlackTree(-10, parent=left_rot.left) + left_rot.left.right = RedBlackTree(5, parent=left_rot.left) + left_rot.left.left.left = RedBlackTree(-20, parent=left_rot.left.left) + left_rot.left.left.right = RedBlackTree(-5, parent=left_rot.left.left) + left_rot.right = RedBlackTree(20, parent=left_rot) + tree = tree.rotate_left() + if tree != left_rot: + return False + tree = tree.rotate_right() + tree = tree.rotate_right() + right_rot = RedBlackTree(-10) + right_rot.left = RedBlackTree(-20, parent=right_rot) + right_rot.right = RedBlackTree(0, parent=right_rot) + right_rot.right.left = RedBlackTree(-5, parent=right_rot.right) + right_rot.right.right = RedBlackTree(10, parent=right_rot.right) + right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.right) + right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right) + return tree == right_rot + + +def test_insertion_speed() -> bool: + """Test that the tree balances inserts to O(log(n)) by doing a lot + of them. + """ + tree = RedBlackTree(-1) + for i in range(300000): + tree = tree.insert(i) + return True + + +def test_insert() -> bool: + """Test the insert() method of the tree correctly balances, colors, + and inserts. + """ + tree = RedBlackTree(0) + tree.insert(8) + tree.insert(-8) + tree.insert(4) + tree.insert(12) + tree.insert(10) + tree.insert(11) + ans = RedBlackTree(0, 0) + ans.left = RedBlackTree(-8, 0, ans) + ans.right = RedBlackTree(8, 1, ans) + ans.right.left = RedBlackTree(4, 0, ans.right) + ans.right.right = RedBlackTree(11, 0, ans.right) + ans.right.right.left = RedBlackTree(10, 1, ans.right.right) + ans.right.right.right = RedBlackTree(12, 1, ans.right.right) + return tree == ans + + +def test_insert_and_search() -> bool: + """Tests searching through the tree for values.""" + tree = RedBlackTree(0) + tree.insert(8) + tree.insert(-8) + tree.insert(4) + tree.insert(12) + tree.insert(10) + tree.insert(11) + if any(i in tree for i in (5, -6, -10, 13)): + return False + return all(i in tree for i in (11, 12, -8, 0)) + + +def test_insert_delete() -> bool: + """Test the insert() and delete() method of the tree.""" + tree = RedBlackTree(0) + tree = tree.insert(-12) + tree = tree.insert(8) + tree = tree.insert(-8) + tree = tree.insert(15) + tree = tree.insert(4) + tree = tree.insert(12) + tree = tree.insert(10) + tree = tree.insert(9) + tree = tree.insert(11) + tree = tree.remove(15) + tree = tree.remove(-12) + tree = tree.remove(9) + if not tree.check_color_properties(): + return False + return list(tree.inorder_traverse()) == [-8, 0, 4, 8, 10, 11, 12] + + +def test_floor_ceil() -> bool: + """Tests the floor and ceiling functions in the tree.""" + tree = RedBlackTree(0) + tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + tuples = [(-20, None, -16), (-10, -16, 0), (8, 8, 8), (50, 24, None)] + for val, floor, ceil in tuples: + if tree.floor(val) != floor or tree.ceil(val) != ceil: + return False + return True + + +def test_min_max() -> bool: + """Tests the min and max functions in the tree.""" + tree = RedBlackTree(0) + tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + return not (tree.get_max() != 22 or tree.get_min() != -16) + + +def test_tree_traversal() -> bool: + """Tests the three different tree traversal functions.""" + tree = RedBlackTree(0) + tree = tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: + return False + if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: + return False + return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0] + + +def test_tree_chaining() -> bool: + """Tests the three different tree chaining functions.""" + tree = RedBlackTree(0) + tree = tree.insert(-16).insert(16).insert(8).insert(24).insert(20).insert(22) + if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: + return False + if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: + return False + return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0] + + +def print_results(msg: str, passes: bool) -> None: + print(str(msg), "works!" if passes else "doesn't work :(") + + +def pytests() -> None: + assert test_rotations() + assert test_insert() + assert test_insert_and_search() + assert test_insert_delete() + assert test_floor_ceil() + assert test_tree_traversal() + assert test_tree_chaining() + + +def main() -> None: + """ + >>> pytests() + """ + import doctest + failures, _ = doctest.testmod() + if failures == 0: + print("All doctests passed!") + else: + print(f"{failures} doctests failed!") + + print_results("Rotating right and left", test_rotations()) + print_results("Inserting", test_insert()) + print_results("Searching", test_insert_and_search()) + print_results("Deleting", test_insert_delete()) + print_results("Floor and ceil", test_floor_ceil()) + print_results("Tree traversal", test_tree_traversal()) + print_results("Tree chaining", test_tree_chaining()) + print("Testing tree balancing...") + print("This should only be a few seconds.") + test_insertion_speed() + print("Done!") + + +if __name__ == "__main__": + main() \ No newline at end of file From e7f3e0a8568539dd2a27c58ff04cfd21ee3b3d7d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:12:14 +0000 Subject: [PATCH 084/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/red_black_tree.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index cf372c62ff32..f3f1f8243c97 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -3,6 +3,7 @@ from collections.abc import Iterator from pprint import pformat + class RedBlackTree: """ A Red-Black tree, which is a self-balancing BST (binary search @@ -839,12 +840,13 @@ def main() -> None: >>> pytests() """ import doctest + failures, _ = doctest.testmod() if failures == 0: print("All doctests passed!") else: print(f"{failures} doctests failed!") - + print_results("Rotating right and left", test_rotations()) print_results("Inserting", test_insert()) print_results("Searching", test_insert_and_search()) @@ -859,4 +861,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() From b6ed3932817fb2cea1c7b861735f1a4a30e3ced5 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:27:35 +0800 Subject: [PATCH 085/156] Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index f3f1f8243c97..f8829deb97a6 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -353,9 +353,7 @@ def check_color_properties(self) -> bool: if not self.check_coloring(): return False # Property 5 - if self.black_height() is None: - return False - return True + if self.black_height() is not None def check_coloring(self) -> bool: """Check if the tree satisfies Red-Black property 4.""" From 00f7847541c159e1138f42e5b4c4ee9b16f25d64 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:29:48 +0800 Subject: [PATCH 086/156] Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index f8829deb97a6..0628d00032dc 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -353,7 +353,7 @@ def check_color_properties(self) -> bool: if not self.check_coloring(): return False # Property 5 - if self.black_height() is not None + if self.black_height() is not None: def check_coloring(self) -> bool: """Check if the tree satisfies Red-Black property 4.""" From 52891a4c527c528c90b5eb650accdc16d0f264c2 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:34:00 +0800 Subject: [PATCH 087/156] Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 0628d00032dc..463b9a390c77 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -353,8 +353,9 @@ def check_color_properties(self) -> bool: if not self.check_coloring(): return False # Property 5 - if self.black_height() is not None: + return self.black_height() is not None + def check_coloring(self) -> bool: """Check if the tree satisfies Red-Black property 4.""" if self.color == 1 and 1 in (color(self.left), color(self.right)): From 99ea24e727fc35d1042c266a19726a0ee0d94f38 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:34:28 +0000 Subject: [PATCH 088/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/red_black_tree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 463b9a390c77..04099c8fc780 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -355,7 +355,6 @@ def check_color_properties(self) -> bool: # Property 5 return self.black_height() is not None - def check_coloring(self) -> bool: """Check if the tree satisfies Red-Black property 4.""" if self.color == 1 and 1 in (color(self.left), color(self.right)): From be02fd551673539bfb3d2de36b9ec778b5797c76 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:39:23 +0800 Subject: [PATCH 089/156] Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 04099c8fc780..a7ad0b89a821 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import Iterator +import doctest from pprint import pformat @@ -837,7 +838,7 @@ def main() -> None: """ >>> pytests() """ - import doctest + failures, _ = doctest.testmod() if failures == 0: From 11c74ec6710c4d776d03e5d32b142bd754fd569b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:39:45 +0000 Subject: [PATCH 090/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/red_black_tree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index a7ad0b89a821..36332dfe9186 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -838,7 +838,6 @@ def main() -> None: """ >>> pytests() """ - failures, _ = doctest.testmod() if failures == 0: From 76b34e8ed65b5ba9c9a2973ebfbf8d8ed5afd78f Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:42:22 +0800 Subject: [PATCH 091/156] Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 36332dfe9186..b4987416d76f 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -1,10 +1,9 @@ from __future__ import annotations -from collections.abc import Iterator import doctest +from collections.abc import Iterator from pprint import pformat - class RedBlackTree: """ A Red-Black tree, which is a self-balancing BST (binary search From 6c499c1948e693acfd4f4a379d9de6fc45c63f13 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:42:45 +0000 Subject: [PATCH 092/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/red_black_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index b4987416d76f..d401b065d5e3 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -4,6 +4,7 @@ from collections.abc import Iterator from pprint import pformat + class RedBlackTree: """ A Red-Black tree, which is a self-balancing BST (binary search From fbd60e63af58573db10dc8ddb349bef5267f1a26 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:48:20 +0800 Subject: [PATCH 093/156] Update binary_search_tree.py --- data_structures/binary_tree/binary_search_tree.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 3f214d0113a4..f3bd1dfe802b 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -93,6 +93,7 @@ from collections.abc import Iterable, Iterator from dataclasses import dataclass +from pprint import pformat from typing import Any, Self @@ -115,7 +116,7 @@ def __iter__(self) -> Iterator[int]: yield from self.right or [] def __repr__(self) -> str: - from pprint import pformat + if self.left is None and self.right is None: return str(self.value) From 91cb294be33b94e1296a7c5af712a0be7d563984 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:48:43 +0000 Subject: [PATCH 094/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/binary_search_tree.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index f3bd1dfe802b..1f6baf5aeeb7 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -116,8 +116,6 @@ def __iter__(self) -> Iterator[int]: yield from self.right or [] def __repr__(self) -> str: - - if self.left is None and self.right is None: return str(self.value) return pformat({f"{self.value}": (self.left, self.right)}, indent=1) From de2c08024155e1412415a3370fba4d8fa19b7faf Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:50:37 +0800 Subject: [PATCH 095/156] Update diff_views_of_binary_tree.py --- data_structures/binary_tree/diff_views_of_binary_tree.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data_structures/binary_tree/diff_views_of_binary_tree.py b/data_structures/binary_tree/diff_views_of_binary_tree.py index 3198d8065918..5b9530388680 100644 --- a/data_structures/binary_tree/diff_views_of_binary_tree.py +++ b/data_structures/binary_tree/diff_views_of_binary_tree.py @@ -173,8 +173,7 @@ def binary_tree_bottom_side_view(root: TreeNode) -> list[int]: >>> binary_tree_bottom_side_view(None) [] """ - from collections import defaultdict - + def breadth_first_search(root: TreeNode, bottom_view: list[int]) -> None: """ A breadth first search traversal with defaultdict ds to append From 1a25ad6fe59890eae1de19621665b06145f3e211 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:51:00 +0000 Subject: [PATCH 096/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/diff_views_of_binary_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_tree/diff_views_of_binary_tree.py b/data_structures/binary_tree/diff_views_of_binary_tree.py index 5b9530388680..450c60a19373 100644 --- a/data_structures/binary_tree/diff_views_of_binary_tree.py +++ b/data_structures/binary_tree/diff_views_of_binary_tree.py @@ -173,7 +173,7 @@ def binary_tree_bottom_side_view(root: TreeNode) -> list[int]: >>> binary_tree_bottom_side_view(None) [] """ - + def breadth_first_search(root: TreeNode, bottom_view: list[int]) -> None: """ A breadth first search traversal with defaultdict ds to append From e6ad02191162f95bca1da793386be83f36a3ed9f Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Fri, 4 Jul 2025 23:53:43 +0800 Subject: [PATCH 097/156] Update avl_tree.py --- data_structures/binary_tree/avl_tree.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 8558305eefe4..5d2018ddd967 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -8,8 +8,10 @@ from __future__ import annotations +import doctest import math import random + from typing import Any @@ -330,7 +332,6 @@ def __str__( def _test() -> None: - import doctest doctest.testmod() From 28a57e81a0d0d5f9715dd80205e8a687eb0bf673 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:54:19 +0000 Subject: [PATCH 098/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/avl_tree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 5d2018ddd967..47710d962540 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -332,7 +332,6 @@ def __str__( def _test() -> None: - doctest.testmod() From c7d93263adf598a4c3c14bef8b191bdbe04d6855 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 00:18:51 +0800 Subject: [PATCH 099/156] Update binary_search_tree.py --- data_structures/binary_tree/binary_search_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 1f6baf5aeeb7..844c2fa74ad6 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -94,6 +94,7 @@ from collections.abc import Iterable, Iterator from dataclasses import dataclass from pprint import pformat + from typing import Any, Self From c8747af8f4737f9207430df9831fabe24bd420e8 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 00:22:08 +0800 Subject: [PATCH 100/156] Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 68 ++++++++++++++----- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index d401b065d5e3..c3d0b5cf4ea6 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -336,24 +336,33 @@ def _remove_repair(self) -> None: self.parent.sibling.color = 0 def check_color_properties(self) -> bool: - """Check if the tree satisfies all Red-Black tree properties. + def check_color_properties(self) -> bool: + """ + Verify that all Red-Black Tree properties are satisfied: + 1. Root node is black + 2. No two consecutive red nodes (red node cannot have red children) + 3. All paths from any node to its leaf descendants have the same number of black nodes Returns: True if all properties are satisfied, False otherwise Examples: - >>> tree = RedBlackTree(0) - >>> tree = tree.insert(1).insert(-1) + >>> tree = RedBlackTree(10).insert(5).insert(15) >>> tree.check_color_properties() True + >>> tree.left.color = 1 # Force invalid state: two consecutive reds + >>> tree.check_color_properties() + False """ - # Property 2 - if self.color: + # Property 1: Root must be black + if self.parent is None and self.color != 0: return False - # Property 4 + + # Property 2: No two consecutive red nodes if not self.check_coloring(): return False - # Property 5 + + # Property 3: All paths have same black height return self.black_height() is not None def check_coloring(self) -> bool: @@ -364,18 +373,41 @@ def check_coloring(self) -> bool: return False return not (self.right and not self.right.check_coloring()) - def black_height(self) -> int | None: - """Calculate the black height of the tree.""" - if self is None or self.left is None or self.right is None: - return 1 - left = RedBlackTree.black_height(self.left) - right = RedBlackTree.black_height(self.right) - if left is None or right is None: - return None - if left != right: - return None - return left + (1 - self.color) + def black_height(self) -> int | None: + """ + Calculate the black height of the tree and verify consistency + - Black height = number of black nodes from current node to any leaf + - Count includes current node if black and all leaf nodes (None are black) + - Returns None if any path has different black height + Returns: + Black height if consistent, None otherwise + + Examples: + >>> tree = RedBlackTree(10, color=0) # Black root + >>> tree.insert(5, color=1) # Red child + >>> tree.black_height() # Paths: [10(black), 5(red), None(black)] = 2 + 2 [10(black), None(black)] = 2 + """ + # Base case: Leaf node (None is always black) + if self is None: + return 1 + + # Leaf node case (both children are None) + if self.left is None and self.right is None: + # Count: current node (if black) + leaf (black) + return 1 + (1 - self.color) # 2 if black, 1 if red + + # Recursive case: Check both subtrees + left_bh = self.left.black_height() if self.left else 1 + right_bh = self.right.black_height() if self.right else 1 + + # Validate consistency + if left_bh is None or right_bh is None or left_bh != right_bh: + return None # Inconsistent black heights + + # Current node's contribution: add 1 if black + return left_bh + (1 - self.color) def __contains__(self, label: int) -> bool: """Check if the tree contains a label. From 8cccecf6ce1c18887ca42036c92e3c573a0aae3b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:22:21 +0000 Subject: [PATCH 101/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/red_black_tree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index c3d0b5cf4ea6..d0ec4ada3c23 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -357,11 +357,11 @@ def check_color_properties(self) -> bool: # Property 1: Root must be black if self.parent is None and self.color != 0: return False - + # Property 2: No two consecutive red nodes if not self.check_coloring(): return False - + # Property 3: All paths have same black height return self.black_height() is not None @@ -392,7 +392,7 @@ def black_height(self) -> int | None: # Base case: Leaf node (None is always black) if self is None: return 1 - + # Leaf node case (both children are None) if self.left is None and self.right is None: # Count: current node (if black) + leaf (black) @@ -405,7 +405,7 @@ def black_height(self) -> int | None: # Validate consistency if left_bh is None or right_bh is None or left_bh != right_bh: return None # Inconsistent black heights - + # Current node's contribution: add 1 if black return left_bh + (1 - self.color) def __contains__(self, label: int) -> bool: From 71cba736f376920f07c11b67c3f0e96c2ddfba3f Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 00:33:38 +0800 Subject: [PATCH 102/156] Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index d0ec4ada3c23..d7d108c5910e 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -335,13 +335,14 @@ def _remove_repair(self) -> None: self.parent.color = 0 self.parent.sibling.color = 0 - def check_color_properties(self) -> bool: + def check_color_properties(self) -> bool: """ Verify that all Red-Black Tree properties are satisfied: - 1. Root node is black - 2. No two consecutive red nodes (red node cannot have red children) - 3. All paths from any node to its leaf descendants have the same number of black nodes + # Root node is black + # No two consecutive red nodes (red node cannot have red children) + # All paths from any node to its leaf descendants have + # the same number of black nodes Returns: True if all properties are satisfied, False otherwise @@ -373,7 +374,7 @@ def check_coloring(self) -> bool: return False return not (self.right and not self.right.check_coloring()) - def black_height(self) -> int | None: + def black_height(self) -> int | None: """ Calculate the black height of the tree and verify consistency - Black height = number of black nodes from current node to any leaf From 4bb794dfd57fbae28eb962f7ee389e995ec3253a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:33:51 +0000 Subject: [PATCH 103/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/red_black_tree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index d7d108c5910e..1638353d321a 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -335,13 +335,13 @@ def _remove_repair(self) -> None: self.parent.color = 0 self.parent.sibling.color = 0 - + def check_color_properties(self) -> bool: """ Verify that all Red-Black Tree properties are satisfied: # Root node is black # No two consecutive red nodes (red node cannot have red children) - # All paths from any node to its leaf descendants have + # All paths from any node to its leaf descendants have # the same number of black nodes Returns: From 17ea8202262e358727d8cf1458de96d0c467e1f4 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 00:40:32 +0800 Subject: [PATCH 104/156] Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 1638353d321a..cf10bd34f7a6 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -336,36 +336,28 @@ def _remove_repair(self) -> None: self.parent.sibling.color = 0 - def check_color_properties(self) -> bool: + def check_color_properties(self) -> bool: """ Verify that all Red-Black Tree properties are satisfied: - # Root node is black - # No two consecutive red nodes (red node cannot have red children) - # All paths from any node to its leaf descendants have - # the same number of black nodes - + 1. Root node is black + 2. No two consecutive red nodes + 3. All paths have same black height + Returns: True if all properties are satisfied, False otherwise - - Examples: - >>> tree = RedBlackTree(10).insert(5).insert(15) - >>> tree.check_color_properties() - True - >>> tree.left.color = 1 # Force invalid state: two consecutive reds - >>> tree.check_color_properties() - False """ # Property 1: Root must be black if self.parent is None and self.color != 0: return False - + # Property 2: No two consecutive red nodes if not self.check_coloring(): return False - + # Property 3: All paths have same black height return self.black_height() is not None + def check_coloring(self) -> bool: """Check if the tree satisfies Red-Black property 4.""" if self.color == 1 and 1 in (color(self.left), color(self.right)): From 2762dbc4f65623b48ee13c409585ffa4663f9c56 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 00:41:38 +0800 Subject: [PATCH 105/156] Update red_black_tree.py --- data_structures/binary_tree/red_black_tree.py | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index cf10bd34f7a6..10c02daa5ce6 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -370,36 +370,25 @@ def black_height(self) -> int | None: """ Calculate the black height of the tree and verify consistency - Black height = number of black nodes from current node to any leaf - - Count includes current node if black and all leaf nodes (None are black) - Returns None if any path has different black height - + Returns: Black height if consistent, None otherwise - - Examples: - >>> tree = RedBlackTree(10, color=0) # Black root - >>> tree.insert(5, color=1) # Red child - >>> tree.black_height() # Paths: [10(black), 5(red), None(black)] = 2 - 2 [10(black), None(black)] = 2 """ - # Base case: Leaf node (None is always black) - if self is None: - return 1 - # Leaf node case (both children are None) if self.left is None and self.right is None: # Count: current node (if black) + leaf (black) return 1 + (1 - self.color) # 2 if black, 1 if red - - # Recursive case: Check both subtrees + + # Get black heights from both subtrees left_bh = self.left.black_height() if self.left else 1 right_bh = self.right.black_height() if self.right else 1 - + # Validate consistency if left_bh is None or right_bh is None or left_bh != right_bh: - return None # Inconsistent black heights - - # Current node's contribution: add 1 if black + return None + + # Add current node's contribution (1 if black, 0 if red) return left_bh + (1 - self.color) def __contains__(self, label: int) -> bool: """Check if the tree contains a label. From 03d7920e17f38a9ee63379fbc50f77e8e5139b1a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:42:39 +0000 Subject: [PATCH 106/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/red_black_tree.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 10c02daa5ce6..1039be892e3a 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -335,29 +335,27 @@ def _remove_repair(self) -> None: self.parent.color = 0 self.parent.sibling.color = 0 - def check_color_properties(self) -> bool: """ Verify that all Red-Black Tree properties are satisfied: 1. Root node is black 2. No two consecutive red nodes 3. All paths have same black height - + Returns: True if all properties are satisfied, False otherwise """ # Property 1: Root must be black if self.parent is None and self.color != 0: return False - + # Property 2: No two consecutive red nodes if not self.check_coloring(): return False - + # Property 3: All paths have same black height return self.black_height() is not None - def check_coloring(self) -> bool: """Check if the tree satisfies Red-Black property 4.""" if self.color == 1 and 1 in (color(self.left), color(self.right)): @@ -371,7 +369,7 @@ def black_height(self) -> int | None: Calculate the black height of the tree and verify consistency - Black height = number of black nodes from current node to any leaf - Returns None if any path has different black height - + Returns: Black height if consistent, None otherwise """ @@ -379,17 +377,18 @@ def black_height(self) -> int | None: if self.left is None and self.right is None: # Count: current node (if black) + leaf (black) return 1 + (1 - self.color) # 2 if black, 1 if red - + # Get black heights from both subtrees left_bh = self.left.black_height() if self.left else 1 right_bh = self.right.black_height() if self.right else 1 - + # Validate consistency if left_bh is None or right_bh is None or left_bh != right_bh: return None - + # Add current node's contribution (1 if black, 0 if red) return left_bh + (1 - self.color) + def __contains__(self, label: int) -> bool: """Check if the tree contains a label. From 2adfd0b407f350ba9d491be34f65ae4d1613a99f Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 00:44:09 +0800 Subject: [PATCH 107/156] Update avl_tree.py --- data_structures/binary_tree/avl_tree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 47710d962540..bc1090cf7d97 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -11,7 +11,6 @@ import doctest import math import random - from typing import Any From 7504e8d1f388f7f51862679ae18020dad7f6bcf6 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 00:44:45 +0800 Subject: [PATCH 108/156] Update binary_search_tree.py --- data_structures/binary_tree/binary_search_tree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 844c2fa74ad6..1f6baf5aeeb7 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -94,7 +94,6 @@ from collections.abc import Iterable, Iterator from dataclasses import dataclass from pprint import pformat - from typing import Any, Self From 0ea5a472ad3c0b1207f756d113d4e2ff3ebc4226 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 01:02:49 +0800 Subject: [PATCH 109/156] Update treap.py --- data_structures/binary_tree/treap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 3114c6fa1c26..afcb7e633533 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -1,8 +1,9 @@ from __future__ import annotations +import doctest +from pprint import pformat from random import random - class Node: """ Treap's node @@ -16,7 +17,7 @@ def __init__(self, value: int | None = None): self.right: Node | None = None def __repr__(self) -> str: - from pprint import pformat + if self.left is None and self.right is None: return f"'{self.value}: {self.prior:.5}'" @@ -173,7 +174,6 @@ def main() -> None: if __name__ == "__main__": - import doctest doctest.testmod() main() From 44ce0500419a2ce9931bdc3affff7ed74321e355 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 17:03:55 +0000 Subject: [PATCH 110/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/binary_tree/treap.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index afcb7e633533..7755188d1524 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -4,6 +4,7 @@ from pprint import pformat from random import random + class Node: """ Treap's node @@ -17,8 +18,6 @@ def __init__(self, value: int | None = None): self.right: Node | None = None def __repr__(self) -> str: - - if self.left is None and self.right is None: return f"'{self.value}: {self.prior:.5}'" else: @@ -174,6 +173,5 @@ def main() -> None: if __name__ == "__main__": - doctest.testmod() main() From 3bea2fa6a54e8ea99951382f5d9218f2d63c44fc Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 01:21:39 +0800 Subject: [PATCH 111/156] Update local_weighted_learning.py --- .../local_weighted_learning/local_weighted_learning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py index f3056da40e24..634b7019c8ce 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.py +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -33,7 +33,7 @@ import matplotlib.pyplot as plt import numpy as np - +import seaborn as sns def weight_matrix(point: np.ndarray, x_train: np.ndarray, tau: float) -> np.ndarray: """ @@ -134,7 +134,7 @@ def load_data( Load data from seaborn and split it into x and y points >>> pass # No doctests, function is for demo purposes only """ - import seaborn as sns + data = sns.load_dataset(dataset_name) x_data = np.array(data[x_name]) From 42ee813c547ebf542448a23384ff97c94c3b8114 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 17:22:04 +0000 Subject: [PATCH 112/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../local_weighted_learning/local_weighted_learning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine_learning/local_weighted_learning/local_weighted_learning.py b/machine_learning/local_weighted_learning/local_weighted_learning.py index 634b7019c8ce..3f3a4bd0dcf5 100644 --- a/machine_learning/local_weighted_learning/local_weighted_learning.py +++ b/machine_learning/local_weighted_learning/local_weighted_learning.py @@ -35,6 +35,7 @@ import numpy as np import seaborn as sns + def weight_matrix(point: np.ndarray, x_train: np.ndarray, tau: float) -> np.ndarray: """ Calculate the weight of every point in the training data around a given @@ -135,7 +136,6 @@ def load_data( >>> pass # No doctests, function is for demo purposes only """ - data = sns.load_dataset(dataset_name) x_data = np.array(data[x_name]) y_data = np.array(data[y_name]) From b8116a15157f6bd96d2bec446833edd825dccb9c Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 01:30:38 +0800 Subject: [PATCH 113/156] Update sha256.py --- hashes/sha256.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hashes/sha256.py b/hashes/sha256.py index bcc83edca480..100ed985cd2f 100644 --- a/hashes/sha256.py +++ b/hashes/sha256.py @@ -16,6 +16,8 @@ """ import argparse +import doctest +import hashlib import struct import unittest @@ -200,7 +202,7 @@ class SHA256HashTest(unittest.TestCase): """ def test_match_hashes(self) -> None: - import hashlib + msg = bytes("Test String", "utf-8") assert SHA256(msg).hash == hashlib.sha256(msg).hexdigest() @@ -214,7 +216,7 @@ def main() -> None: # unittest.main() - import doctest + doctest.testmod() From 26a06559dea857c99a5aec4f4a19952fe058090d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 17:31:14 +0000 Subject: [PATCH 114/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- hashes/sha256.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hashes/sha256.py b/hashes/sha256.py index 100ed985cd2f..6acd23ef0489 100644 --- a/hashes/sha256.py +++ b/hashes/sha256.py @@ -202,8 +202,6 @@ class SHA256HashTest(unittest.TestCase): """ def test_match_hashes(self) -> None: - - msg = bytes("Test String", "utf-8") assert SHA256(msg).hash == hashlib.sha256(msg).hexdigest() @@ -216,8 +214,6 @@ def main() -> None: # unittest.main() - - doctest.testmod() parser = argparse.ArgumentParser() From d73d647a08012abf9b903edc865ccd178d0ab311 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 01:36:15 +0800 Subject: [PATCH 115/156] Update mfcc.py --- machine_learning/mfcc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/machine_learning/mfcc.py b/machine_learning/mfcc.py index dcc3151d5a1a..4a9603b8a443 100644 --- a/machine_learning/mfcc.py +++ b/machine_learning/mfcc.py @@ -56,11 +56,11 @@ Author: Amir Lavasani """ - import logging import numpy as np import scipy.fftpack as fft +from scipy.io import wavfile from scipy.signal import get_window logging.basicConfig(filename=f"{__file__}.log", level=logging.INFO) @@ -464,7 +464,7 @@ def example(wav_file_path: str = "./path-to-file/sample.wav") -> np.ndarray: Returns: np.ndarray: The computed MFCCs for the audio. """ - from scipy.io import wavfile + # Load the audio from the WAV file sample_rate, audio = wavfile.read(wav_file_path) From ae67342ae7a2be63dfd351c1586946286b8b173b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 17:36:40 +0000 Subject: [PATCH 116/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- machine_learning/mfcc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine_learning/mfcc.py b/machine_learning/mfcc.py index 4a9603b8a443..7c4108e4d37c 100644 --- a/machine_learning/mfcc.py +++ b/machine_learning/mfcc.py @@ -56,6 +56,7 @@ Author: Amir Lavasani """ + import logging import numpy as np @@ -465,7 +466,6 @@ def example(wav_file_path: str = "./path-to-file/sample.wav") -> np.ndarray: np.ndarray: The computed MFCCs for the audio. """ - # Load the audio from the WAV file sample_rate, audio = wavfile.read(wav_file_path) From 92197891b0d2f1548c8d0cc41dc8980597a6fddd Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 01:56:52 +0800 Subject: [PATCH 117/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 374 ++++++++++++++++++++++++++++++----------- 1 file changed, 276 insertions(+), 98 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 33b2529f017b..68cac78641cf 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -1,42 +1,25 @@ """ +Hill Cipher Implementation -Hill Cipher: -The 'HillCipher' class below implements the Hill Cipher algorithm which uses -modern linear algebra techniques to encode and decode text using an encryption -key matrix. +This module provides a complete implementation of the Hill Cipher algorithm, +which uses linear algebra techniques for text encryption and decryption. +The cipher supports alphanumeric characters (A-Z, 0-9) and uses modular +arithmetic with base 36. -Algorithm: -Let the order of the encryption key be N (as it is a square matrix). -Your text is divided into batches of length N and converted to numerical vectors -by a simple mapping starting with A=0 and so on. +Classes: + HillCipher: Implements the Hill Cipher encryption and decryption operations. -The key is then multiplied with the newly created batch vector to obtain the -encoded vector. After each multiplication modular 36 calculations are performed -on the vectors so as to bring the numbers between 0 and 36 and then mapped with -their corresponding alphanumerics. - -While decrypting, the decrypting key is found which is the inverse of the -encrypting key modular 36. The same process is repeated for decrypting to get -the original message back. - -Constraints: -The determinant of the encryption key matrix must be relatively prime w.r.t 36. - -Note: -This implementation only considers alphanumerics in the text. If the length of -the text to be encrypted is not a multiple of the break key(the length of one -batch of letters), the last character of the text is added to the text until the -length of the text reaches a multiple of the break_key. So the text after -decrypting might be a little different than the original text. +Functions: + main: Command-line interface for the Hill Cipher operations. References: -https://apprendre-en-ligne.net/crypto/hill/Hillciph.pdf -https://www.youtube.com/watch?v=kfmNeskzs2o -https://www.youtube.com/watch?v=4RhLNDqcjpA - + https://apprendre-en-ligne.net/crypto/hill/Hillciph.pdf + https://www.youtube.com/watch?v=kfmNeskzs2o + https://www.youtube.com/watch?v=4RhLNDqcjpA """ import string +from typing import Optional import numpy as np @@ -44,55 +27,131 @@ class HillCipher: - key_string = string.ascii_uppercase + string.digits - # This cipher takes alphanumerics into account - # i.e. a total of 36 characters + """ + Implementation of the Hill Cipher algorithm using matrix operations. + + Attributes: + key_string (str): String of valid characters (A-Z and 0-9) + modulus (function): Vectorized function for mod 36 operation + to_int (function): Vectorized rounding function + encrypt_key (np.ndarray): Encryption key matrix + break_key (int): Size of the encryption key matrix (N x N) + + Methods: + replace_letters: Convert character to numerical value + replace_digits: Convert numerical value to character + check_determinant: Validate encryption key determinant + process_text: Prepare text for encryption/decryption + encrypt: Encrypt plaintext using Hill Cipher + make_decrypt_key: Compute decryption key matrix + decrypt: Decrypt ciphertext using Hill Cipher + """ - # take x and return x % len(key_string) + key_string = string.ascii_uppercase + string.digits modulus = np.vectorize(lambda x: x % 36) - to_int = np.vectorize(round) def __init__(self, encrypt_key: np.ndarray) -> None: """ - encrypt_key is an NxN numpy array + Initialize Hill Cipher with encryption key matrix. + + Args: + encrypt_key: Square matrix used for encryption + + Raises: + ValueError: If encryption key is invalid + + Examples: + >>> key = np.array([[2, 5], [1, 6]]) + >>> cipher = HillCipher(key) + >>> cipher.break_key + 2 """ - self.encrypt_key = self.modulus(encrypt_key) # mod36 calc's on the encrypt key - self.check_determinant() # validate the determinant of the encryption key + self.encrypt_key = self.modulus(encrypt_key) + self.check_determinant() self.break_key = encrypt_key.shape[0] def replace_letters(self, letter: str) -> int: """ - >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) - >>> hill_cipher.replace_letters('T') - 19 - >>> hill_cipher.replace_letters('0') - 26 + Convert character to its numerical equivalent. + + Args: + letter: Character to convert (A-Z or 0-9) + + Returns: + Numerical value (0-35) + + Examples: + >>> key = np.array([[2, 5], [1, 6]]) + >>> cipher = HillCipher(key) + >>> cipher.replace_letters('A') + 0 + >>> cipher.replace_letters('Z') + 25 + >>> cipher.replace_letters('0') + 26 + >>> cipher.replace_letters('9') + 35 """ return self.key_string.index(letter) def replace_digits(self, num: int) -> str: """ - >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) - >>> hill_cipher.replace_digits(19) - 'T' - >>> hill_cipher.replace_digits(26) - '0' + Convert numerical value to its character equivalent. + + Args: + num: Numerical value (0-35) + + Returns: + Character equivalent (A-Z or 0-9) + + Examples: + >>> key = np.array([[2, 5], [1, 6]]) + >>> cipher = HillCipher(key) + >>> cipher.replace_digits(0) + 'A' + >>> cipher.replace_digits(25) + 'Z' + >>> cipher.replace_digits(26) + '0' + >>> cipher.replace_digits(35) + '9' """ return self.key_string[round(num)] def check_determinant(self) -> None: """ - >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) - >>> hill_cipher.check_determinant() + Validate encryption key determinant. + + The determinant must be coprime with 36 for the key to be valid. + + Raises: + ValueError: If determinant is not coprime with 36 + + Examples: + >>> key = np.array([[2, 5], [1, 6]]) + >>> cipher = HillCipher(key) # Valid key + + >>> invalid_key = np.array([[2, 2], [1, 1]]) + >>> HillCipher(invalid_key) # Determinant 0 + Traceback (most recent call last): + ... + ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. + Try another key. + + >>> invalid_key2 = np.array([[4, 2], [6, 3]]) + >>> HillCipher(invalid_key2) # Determinant 0 + Traceback (most recent call last): + ... + ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. + Try another key. """ - det = round(np.linalg.det(self.encrypt_key)) - + det = int(round(np.linalg.det(self.encrypt_key))) if det < 0: det = det % len(self.key_string) req_l = len(self.key_string) - if greatest_common_divisor(det, len(self.key_string)) != 1: + if greatest_common_divisor(det, req_l) != 1: msg = ( f"determinant modular {req_l} of encryption key({det}) " f"is not co prime w.r.t {req_l}.\nTry another key." @@ -101,85 +160,178 @@ def check_determinant(self) -> None: def process_text(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) - >>> hill_cipher.process_text('Testing Hill Cipher') - 'TESTINGHILLCIPHERR' - >>> hill_cipher.process_text('hello') - 'HELLOO' + Prepare text for encryption/decryption by: + 1. Converting to uppercase + 2. Removing invalid characters + 3. Padding with last character to make length multiple of key size + + Args: + text: Text to process + + Returns: + Processed text ready for encryption/decryption + + Examples: + >>> key = np.array([[2, 5], [1, 6]]) + >>> cipher = HillCipher(key) + >>> cipher.process_text('Test!123') + 'TEST123' + >>> cipher.process_text('hello') + 'HELLOO' + >>> cipher.process_text('a') + 'AA' + >>> cipher.process_text('abc') + 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + + # Handle empty input case + if not chars: + return "" + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) - >>> hill_cipher.encrypt('testing hill cipher') - 'WHXYJOLM9C6XT085LL' - >>> hill_cipher.encrypt('hello') - '85FF00' + Encrypt plaintext using Hill Cipher. + + Args: + text: Plaintext to encrypt + + Returns: + Encrypted ciphertext + + Examples: + >>> key = np.array([[2, 5], [1, 6]]) + >>> cipher = HillCipher(key) + >>> cipher.encrypt('Test') + '4Q6J' + >>> cipher.encrypt('Hello World') + '85FF00V4ZAH8' + >>> cipher.encrypt('ABC') + 'A0K' + >>> cipher.encrypt('123') + '0RZ' + >>> cipher.encrypt('a') + 'PP' """ text = self.process_text(text.upper()) + if not text: + return "" + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): + # Extract batch of characters batch = text[i : i + self.break_key] + + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[ - 0 - ] + + # Matrix multiplication and mod 36 + product = self.encrypt_key.dot(batch_vec) + batch_encrypted = self.modulus(product).T.tolist()[0] + + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted ) encrypted += encrypted_batch return encrypted - def make_decrypt_key(self) -> np.ndarray: """ - >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) - >>> hill_cipher.make_decrypt_key() - array([[ 6, 25], - [ 5, 26]]) + Compute decryption key matrix from encryption key. + + Returns: + Decryption key matrix + + Raises: + ValueError: If modular inverse doesn't exist + + Examples: + >>> key = np.array([[2, 5], [1, 6]]) + >>> cipher = HillCipher(key) + >>> cipher.make_decrypt_key() + array([[ 6, 25], + [ 5, 26]]) + + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) + >>> cipher3 = HillCipher(key3x3) + >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid + Traceback (most recent call last): + ... + ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. + Try another key. """ - det = round(np.linalg.det(self.encrypt_key)) - + det = int(round(np.linalg.det(self.encrypt_key))) if det < 0: det = det % len(self.key_string) - det_inv = None + + det_inv: Optional[int] = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: det_inv = i break - inv_key = ( - det_inv * np.linalg.det(self.encrypt_key) * np.linalg.inv(self.encrypt_key) - ) + if det_inv is None: + raise ValueError("Modular inverse does not exist for decryption key") + det_float = np.linalg.det(self.encrypt_key) + inv_key = det_inv * det_float * np.linalg.inv(self.encrypt_key) return self.to_int(self.modulus(inv_key)) def decrypt(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]])) - >>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL') - 'TESTINGHILLCIPHERR' - >>> hill_cipher.decrypt('85FF00') - 'HELLOO' + Decrypt ciphertext using Hill Cipher. + + Args: + text: Ciphertext to decrypt + + Returns: + Decrypted plaintext + + Examples: + >>> key = np.array([[2, 5], [1, 6]]) + >>> cipher = HillCipher(key) + >>> cipher.decrypt('4Q6J') + 'TEST' + >>> cipher.decrypt('85FF00V4ZAH8') + 'HELLOWORLDD' + >>> cipher.decrypt('A0K') + 'ABCC' + >>> cipher.decrypt('0RZ') + '1233' + >>> cipher.decrypt('PP') + 'AA' + >>> cipher.decrypt('') + '' """ - decrypt_key = self.make_decrypt_key() text = self.process_text(text.upper()) + if not text: + return "" + + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): + # Extract batch of characters batch = text[i : i + self.break_key] + + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - batch_decrypted = self.modulus(decrypt_key.dot(batch_vec)).T.tolist()[0] + + # Matrix multiplication and mod 36 + product = decrypt_key.dot(batch_vec) + batch_decrypted = self.modulus(product).T.tolist()[0] + + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted ) @@ -189,31 +341,57 @@ def decrypt(self, text: str) -> str: def main() -> None: + """ + Command-line interface for Hill Cipher operations. + + Steps: + 1. User inputs encryption key size + 2. User inputs encryption key matrix rows + 3. User chooses encryption or decryption + 4. User inputs text to process + 5. Program outputs result + """ n = int(input("Enter the order of the encryption key: ")) hill_matrix = [] print("Enter each row of the encryption key with space separated integers") - for _ in range(n): - row = [int(x) for x in input().split()] + for i in range(n): + row = [int(x) for x in input(f"Row {i+1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) - print("Would you like to encrypt or decrypt some text? (1 or 2)") - option = input("\n1. Encrypt\n2. Decrypt\n") + print("\nWould you like to encrypt or decrypt some text?") + option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") + if option == "1": - text_e = input("What text would you like to encrypt?: ") - print("Your encrypted text is:") - print(hc.encrypt(text_e)) + text = input("\nEnter text to encrypt: ") + print("\nEncrypted text:") + print(hc.encrypt(text)) elif option == "2": - text_d = input("What text would you like to decrypt?: ") - print("Your decrypted text is:") - print(hc.decrypt(text_d)) + text = input("\nEnter text to decrypt: ") + print("\nDecrypted text:") + print(hc.decrypt(text)) + else: + print("Invalid option selected") if __name__ == "__main__": import doctest - doctest.testmod() - + + print("\nRunning sample tests...") + key = np.array([[2, 5], [1, 6]]) + cipher = HillCipher(key) + + # Test encryption/decryption round trip + plaintext = "HELLO123" + encrypted = cipher.encrypt(plaintext) + decrypted = cipher.decrypt(encrypted) + + print(f"\nOriginal text: {plaintext}") + print(f"Encrypted text: {encrypted}") + print(f"Decrypted text: {decrypted}") + + # Run CLI interface main() From bea40aa516e9155db6f7803705d642e03e42a0c0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 17:57:15 +0000 Subject: [PATCH 118/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/hill_cipher.py | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 68cac78641cf..052c2cbc1bbe 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -184,15 +184,15 @@ def process_text(self, text: str) -> str: 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + # Handle empty input case if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: @@ -222,21 +222,21 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = self.encrypt_key.dot(batch_vec) batch_encrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted @@ -244,6 +244,7 @@ def encrypt(self, text: str) -> str: encrypted += encrypted_batch return encrypted + def make_decrypt_key(self) -> np.ndarray: """ Compute decryption key matrix from encryption key. @@ -260,7 +261,7 @@ def make_decrypt_key(self) -> np.ndarray: >>> cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) - + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> cipher3 = HillCipher(key3x3) >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid @@ -272,7 +273,7 @@ def make_decrypt_key(self) -> np.ndarray: det = int(round(np.linalg.det(self.encrypt_key))) if det < 0: det = det % len(self.key_string) - + det_inv: Optional[int] = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: @@ -315,22 +316,22 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = decrypt_key.dot(batch_vec) batch_decrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -343,7 +344,7 @@ def decrypt(self, text: str) -> str: def main() -> None: """ Command-line interface for Hill Cipher operations. - + Steps: 1. User inputs encryption key size 2. User inputs encryption key matrix rows @@ -356,14 +357,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i+1}: ").split()] + row = [int(x) for x in input(f"Row {i + 1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -378,20 +379,21 @@ def main() -> None: if __name__ == "__main__": import doctest + doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From 3f70e9496d2a8af8fd33bffe54f231d59b9ef0f5 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:03:55 +0800 Subject: [PATCH 119/156] Update requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 66b5d8a6b94e..39359658d970 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ sympy tweepy typing_extensions xgboost +seaborn From 2aa8610f771175494eb6dea43cff1a272866f273 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:04:17 +0000 Subject: [PATCH 120/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 39359658d970..53b19a980dd3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,10 +11,10 @@ pandas pillow rich scikit-learn +seaborn sphinx-pyproject statsmodels sympy tweepy typing_extensions xgboost -seaborn From 4268f2e6d93df2167077b2b4ec8a23ec10174333 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:07:37 +0800 Subject: [PATCH 121/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 71 +++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 052c2cbc1bbe..2da11ef1a5e8 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -19,10 +19,7 @@ """ import string -from typing import Optional - import numpy as np - from maths.greatest_common_divisor import greatest_common_divisor @@ -136,25 +133,27 @@ def check_determinant(self) -> None: >>> HillCipher(invalid_key) # Determinant 0 Traceback (most recent call last): ... - ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. - Try another key. + ValueError: determinant modular 36 of encryption key(0) is not co prime + w.r.t 36. Try another key. >>> invalid_key2 = np.array([[4, 2], [6, 3]]) >>> HillCipher(invalid_key2) # Determinant 0 Traceback (most recent call last): ... - ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. - Try another key. + ValueError: determinant modular 36 of encryption key(0) is not co prime + w.r.t 36. Try another key. """ - det = int(round(np.linalg.det(self.encrypt_key))) + det = round(np.linalg.det(self.encrypt_key)) + det = int(det) # Convert to int after rounding float + if det < 0: det = det % len(self.key_string) req_l = len(self.key_string) if greatest_common_divisor(det, req_l) != 1: msg = ( - f"determinant modular {req_l} of encryption key({det}) " - f"is not co prime w.r.t {req_l}.\nTry another key." + f"determinant modular {req_l} of encryption key({det}) is not co prime " + f"w.r.t {req_l}.\nTry another key." ) raise ValueError(msg) @@ -184,15 +183,15 @@ def process_text(self, text: str) -> str: 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + # Handle empty input case if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: @@ -222,21 +221,21 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = self.encrypt_key.dot(batch_vec) batch_encrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted @@ -245,6 +244,7 @@ def encrypt(self, text: str) -> str: return encrypted + def make_decrypt_key(self) -> np.ndarray: """ Compute decryption key matrix from encryption key. @@ -261,20 +261,22 @@ def make_decrypt_key(self) -> np.ndarray: >>> cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) - + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> cipher3 = HillCipher(key3x3) >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid Traceback (most recent call last): ... - ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. - Try another key. + ValueError: determinant modular 36 of encryption key(0) is not co prime + w.r.t 36. Try another key. """ - det = int(round(np.linalg.det(self.encrypt_key))) + det = round(np.linalg.det(self.encrypt_key)) + det = int(det) # Convert to int after rounding float + if det < 0: det = det % len(self.key_string) - - det_inv: Optional[int] = None + + det_inv: int | None = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: det_inv = i @@ -316,22 +318,22 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = decrypt_key.dot(batch_vec) batch_decrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -344,7 +346,7 @@ def decrypt(self, text: str) -> str: def main() -> None: """ Command-line interface for Hill Cipher operations. - + Steps: 1. User inputs encryption key size 2. User inputs encryption key matrix rows @@ -357,14 +359,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i + 1}: ").split()] + row = [int(x) for x in input(f"Row {i+1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -379,21 +381,20 @@ def main() -> None: if __name__ == "__main__": import doctest - doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From 30d5ff2fd6f1b7486ee83556753e69d28305cab6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:08:05 +0000 Subject: [PATCH 122/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/hill_cipher.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 2da11ef1a5e8..c4539242001b 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -183,15 +183,15 @@ def process_text(self, text: str) -> str: 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + # Handle empty input case if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: @@ -221,21 +221,21 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = self.encrypt_key.dot(batch_vec) batch_encrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted @@ -244,7 +244,6 @@ def encrypt(self, text: str) -> str: return encrypted - def make_decrypt_key(self) -> np.ndarray: """ Compute decryption key matrix from encryption key. @@ -261,7 +260,7 @@ def make_decrypt_key(self) -> np.ndarray: >>> cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) - + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> cipher3 = HillCipher(key3x3) >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid @@ -275,7 +274,7 @@ def make_decrypt_key(self) -> np.ndarray: if det < 0: det = det % len(self.key_string) - + det_inv: int | None = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: @@ -318,22 +317,22 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = decrypt_key.dot(batch_vec) batch_decrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -346,7 +345,7 @@ def decrypt(self, text: str) -> str: def main() -> None: """ Command-line interface for Hill Cipher operations. - + Steps: 1. User inputs encryption key size 2. User inputs encryption key matrix rows @@ -359,14 +358,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i+1}: ").split()] + row = [int(x) for x in input(f"Row {i + 1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -381,20 +380,21 @@ def main() -> None: if __name__ == "__main__": import doctest + doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From fa453bc1073a29ed49ec74707b3e04347e4591cf Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:09:55 +0800 Subject: [PATCH 123/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index c4539242001b..917be89370ce 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -19,6 +19,7 @@ """ import string + import numpy as np from maths.greatest_common_divisor import greatest_common_divisor From a24b482c4ca4407ce005f25af01cc50bd68bf74d Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:10:50 +0800 Subject: [PATCH 124/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 917be89370ce..57c18073828e 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -21,6 +21,7 @@ import string import numpy as np + from maths.greatest_common_divisor import greatest_common_divisor From fa54f68550034a34c7123b2e4a4c8822c86071aa Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:22:44 +0800 Subject: [PATCH 125/156] Update pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2ead5cd51ae8..9bb60b807071 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ "tweepy>=4.14", "typing-extensions>=4.12.2", "xgboost>=2.1.3", + "seaborn>=0.13.2", ] [dependency-groups] From d971725b1175d2503d34836125f0a1c90fae7fd1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:23:07 +0000 Subject: [PATCH 126/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9bb60b807071..afca7514fb00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,13 +22,13 @@ dependencies = [ "pillow>=11", "rich>=13.9.4", "scikit-learn>=1.5.2", + "seaborn>=0.13.2", "sphinx-pyproject>=0.3", "statsmodels>=0.14.4", "sympy>=1.13.3", "tweepy>=4.14", "typing-extensions>=4.12.2", "xgboost>=2.1.3", - "seaborn>=0.13.2", ] [dependency-groups] From 6e836566ff5d1c14ce9885e32c506eb32fa549f1 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:28:04 +0800 Subject: [PATCH 127/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 61 ++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 57c18073828e..ac72a839aa94 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -19,9 +19,7 @@ """ import string - import numpy as np - from maths.greatest_common_divisor import greatest_common_divisor @@ -145,9 +143,10 @@ def check_determinant(self) -> None: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - det = round(np.linalg.det(self.encrypt_key)) - det = int(det) # Convert to int after rounding float - + # Optimized determinant calculation to avoid redundant rounding + det_value = np.linalg.det(self.encrypt_key) + det = int(round(det_value)) if not det_value.is_integer() else int(det_value) + if det < 0: det = det % len(self.key_string) @@ -158,7 +157,6 @@ def check_determinant(self) -> None: f"w.r.t {req_l}.\nTry another key." ) raise ValueError(msg) - def process_text(self, text: str) -> str: """ Prepare text for encryption/decryption by: @@ -185,17 +183,16 @@ def process_text(self, text: str) -> str: 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + # Handle empty input case if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) - def encrypt(self, text: str) -> str: """ Encrypt plaintext using Hill Cipher. @@ -223,21 +220,21 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = self.encrypt_key.dot(batch_vec) batch_encrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted @@ -262,7 +259,7 @@ def make_decrypt_key(self) -> np.ndarray: >>> cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) - + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> cipher3 = HillCipher(key3x3) >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid @@ -271,12 +268,13 @@ def make_decrypt_key(self) -> np.ndarray: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - det = round(np.linalg.det(self.encrypt_key)) - det = int(det) # Convert to int after rounding float - + # Optimized determinant calculation to avoid redundant rounding + det_value = np.linalg.det(self.encrypt_key) + det = int(round(det_value)) if not det_value.is_integer() else int(det_value) + if det < 0: det = det % len(self.key_string) - + det_inv: int | None = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: @@ -319,22 +317,22 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = decrypt_key.dot(batch_vec) batch_decrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -344,10 +342,11 @@ def decrypt(self, text: str) -> str: return decrypted + def main() -> None: """ Command-line interface for Hill Cipher operations. - + Steps: 1. User inputs encryption key size 2. User inputs encryption key matrix rows @@ -360,14 +359,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i + 1}: ").split()] + row = [int(x) for x in input(f"Row {i+1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -379,24 +378,22 @@ def main() -> None: else: print("Invalid option selected") - if __name__ == "__main__": import doctest - doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From 5fa46b80af01d480c34cb84d24888574355182ae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:28:27 +0000 Subject: [PATCH 128/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/hill_cipher.py | 49 ++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index ac72a839aa94..c9e1337673c3 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -146,7 +146,7 @@ def check_determinant(self) -> None: # Optimized determinant calculation to avoid redundant rounding det_value = np.linalg.det(self.encrypt_key) det = int(round(det_value)) if not det_value.is_integer() else int(det_value) - + if det < 0: det = det % len(self.key_string) @@ -157,6 +157,7 @@ def check_determinant(self) -> None: f"w.r.t {req_l}.\nTry another key." ) raise ValueError(msg) + def process_text(self, text: str) -> str: """ Prepare text for encryption/decryption by: @@ -183,16 +184,17 @@ def process_text(self, text: str) -> str: 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + # Handle empty input case if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) + def encrypt(self, text: str) -> str: """ Encrypt plaintext using Hill Cipher. @@ -220,21 +222,21 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = self.encrypt_key.dot(batch_vec) batch_encrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted @@ -259,7 +261,7 @@ def make_decrypt_key(self) -> np.ndarray: >>> cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) - + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> cipher3 = HillCipher(key3x3) >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid @@ -271,10 +273,10 @@ def make_decrypt_key(self) -> np.ndarray: # Optimized determinant calculation to avoid redundant rounding det_value = np.linalg.det(self.encrypt_key) det = int(round(det_value)) if not det_value.is_integer() else int(det_value) - + if det < 0: det = det % len(self.key_string) - + det_inv: int | None = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: @@ -317,22 +319,22 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = decrypt_key.dot(batch_vec) batch_decrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -342,11 +344,10 @@ def decrypt(self, text: str) -> str: return decrypted - def main() -> None: """ Command-line interface for Hill Cipher operations. - + Steps: 1. User inputs encryption key size 2. User inputs encryption key matrix rows @@ -359,14 +360,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i+1}: ").split()] + row = [int(x) for x in input(f"Row {i + 1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -378,22 +379,24 @@ def main() -> None: else: print("Invalid option selected") + if __name__ == "__main__": import doctest + doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From 8185cc0408b4cbe43af83845c9633fd1aea85d28 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:29:43 +0800 Subject: [PATCH 129/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index c9e1337673c3..e4b318340356 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -19,6 +19,7 @@ """ import string + import numpy as np from maths.greatest_common_divisor import greatest_common_divisor From a5cc16e9a2d5798d51565d6920302d6ee823f6b5 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:35:08 +0800 Subject: [PATCH 130/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 59 +++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index e4b318340356..49f01fcf4ffd 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -18,9 +18,13 @@ https://www.youtube.com/watch?v=4RhLNDqcjpA """ +# Standard library imports import string +# Third-party imports import numpy as np + +# Local application imports from maths.greatest_common_divisor import greatest_common_divisor @@ -117,6 +121,7 @@ def replace_digits(self, num: int) -> str: """ return self.key_string[round(num)] + def check_determinant(self) -> None: """ Validate encryption key determinant. @@ -144,10 +149,10 @@ def check_determinant(self) -> None: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - # Optimized determinant calculation to avoid redundant rounding det_value = np.linalg.det(self.encrypt_key) - det = int(round(det_value)) if not det_value.is_integer() else int(det_value) - + # Only round if necessary + det = int(det_value) if det_value.is_integer() else int(round(det_value)) + if det < 0: det = det % len(self.key_string) @@ -159,6 +164,7 @@ def check_determinant(self) -> None: ) raise ValueError(msg) + def process_text(self, text: str) -> str: """ Prepare text for encryption/decryption by: @@ -185,15 +191,15 @@ def process_text(self, text: str) -> str: 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + # Handle empty input case if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: @@ -223,21 +229,21 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = self.encrypt_key.dot(batch_vec) batch_encrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted @@ -262,7 +268,7 @@ def make_decrypt_key(self) -> np.ndarray: >>> cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) - + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> cipher3 = HillCipher(key3x3) >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid @@ -271,13 +277,13 @@ def make_decrypt_key(self) -> np.ndarray: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - # Optimized determinant calculation to avoid redundant rounding det_value = np.linalg.det(self.encrypt_key) - det = int(round(det_value)) if not det_value.is_integer() else int(det_value) - + # Only round if necessary + det = int(det_value) if det_value.is_integer() else int(round(det_value)) + if det < 0: det = det % len(self.key_string) - + det_inv: int | None = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: @@ -320,22 +326,22 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = decrypt_key.dot(batch_vec) batch_decrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -348,7 +354,7 @@ def decrypt(self, text: str) -> str: def main() -> None: """ Command-line interface for Hill Cipher operations. - + Steps: 1. User inputs encryption key size 2. User inputs encryption key matrix rows @@ -361,14 +367,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i + 1}: ").split()] + row = [int(x) for x in input(f"Row {i+1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -383,21 +389,20 @@ def main() -> None: if __name__ == "__main__": import doctest - doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From 77e427ea402200e165c73f5c8a3a1e959c41f9a7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:37:39 +0000 Subject: [PATCH 131/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/hill_cipher.py | 47 +++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 49f01fcf4ffd..c49e0caec19a 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -121,7 +121,6 @@ def replace_digits(self, num: int) -> str: """ return self.key_string[round(num)] - def check_determinant(self) -> None: """ Validate encryption key determinant. @@ -152,7 +151,7 @@ def check_determinant(self) -> None: det_value = np.linalg.det(self.encrypt_key) # Only round if necessary det = int(det_value) if det_value.is_integer() else int(round(det_value)) - + if det < 0: det = det % len(self.key_string) @@ -164,7 +163,6 @@ def check_determinant(self) -> None: ) raise ValueError(msg) - def process_text(self, text: str) -> str: """ Prepare text for encryption/decryption by: @@ -191,15 +189,15 @@ def process_text(self, text: str) -> str: 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + # Handle empty input case if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: @@ -229,21 +227,21 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = self.encrypt_key.dot(batch_vec) batch_encrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted @@ -268,7 +266,7 @@ def make_decrypt_key(self) -> np.ndarray: >>> cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) - + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> cipher3 = HillCipher(key3x3) >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid @@ -280,10 +278,10 @@ def make_decrypt_key(self) -> np.ndarray: det_value = np.linalg.det(self.encrypt_key) # Only round if necessary det = int(det_value) if det_value.is_integer() else int(round(det_value)) - + if det < 0: det = det % len(self.key_string) - + det_inv: int | None = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: @@ -326,22 +324,22 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = decrypt_key.dot(batch_vec) batch_decrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -354,7 +352,7 @@ def decrypt(self, text: str) -> str: def main() -> None: """ Command-line interface for Hill Cipher operations. - + Steps: 1. User inputs encryption key size 2. User inputs encryption key matrix rows @@ -367,14 +365,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i+1}: ").split()] + row = [int(x) for x in input(f"Row {i + 1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -389,20 +387,21 @@ def main() -> None: if __name__ == "__main__": import doctest + doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From b1d0d1f85b03b558a2730695d37880a918e32e82 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:51:05 +0800 Subject: [PATCH 132/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 62 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index c49e0caec19a..2579cf39906d 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -149,19 +149,18 @@ def check_determinant(self) -> None: w.r.t 36. Try another key. """ det_value = np.linalg.det(self.encrypt_key) - # Only round if necessary - det = int(det_value) if det_value.is_integer() else int(round(det_value)) - - if det < 0: - det = det % len(self.key_string) - - req_l = len(self.key_string) - if greatest_common_divisor(det, req_l) != 1: - msg = ( - f"determinant modular {req_l} of encryption key({det}) is not co prime " - f"w.r.t {req_l}.\nTry another key." - ) - raise ValueError(msg) + det = int(round(det_value)) + + if det < 0: + det = det % len(self.key_string) + + req_l = len(self.key_string) + if greatest_common_divisor(det, req_l) != 1: + msg = ( + f"determinant modular {req_l} of encryption key({det}) is not co prime " + f"w.r.t {req_l}.\nTry another key." + ) + raise ValueError(msg) def process_text(self, text: str) -> str: """ @@ -276,24 +275,25 @@ def make_decrypt_key(self) -> np.ndarray: w.r.t 36. Try another key. """ det_value = np.linalg.det(self.encrypt_key) - # Only round if necessary - det = int(det_value) if det_value.is_integer() else int(round(det_value)) - - if det < 0: - det = det % len(self.key_string) - - det_inv: int | None = None - for i in range(len(self.key_string)): - if (det * i) % len(self.key_string) == 1: - det_inv = i - break - - if det_inv is None: - raise ValueError("Modular inverse does not exist for decryption key") - - det_float = np.linalg.det(self.encrypt_key) - inv_key = det_inv * det_float * np.linalg.inv(self.encrypt_key) - return self.to_int(self.modulus(inv_key)) + # 直接取整并转换为整数 + det = int(round(det_value)) + + if det < 0: + det = det % len(self.key_string) + + det_inv: int | None = None + for i in range(len(self.key_string)): + if (det * i) % len(self.key_string) == 1: + det_inv = i + break + + if det_inv is None: + raise ValueError("Modular inverse does not exist for decryption key") + + det_float = np.linalg.det(self.encrypt_key) + inv_key = det_inv * det_float * np.linalg.inv(self.encrypt_key) + return self.to_int(self.modulus(inv_key)) + def decrypt(self, text: str) -> str: """ From 9ed3bbe7c050aff647590721f1eb9856ed6771ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:51:28 +0000 Subject: [PATCH 133/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/hill_cipher.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 2579cf39906d..b5dc8f2a8b6b 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -150,7 +150,7 @@ def check_determinant(self) -> None: """ det_value = np.linalg.det(self.encrypt_key) det = int(round(det_value)) - + if det < 0: det = det % len(self.key_string) @@ -275,12 +275,13 @@ def make_decrypt_key(self) -> np.ndarray: w.r.t 36. Try another key. """ det_value = np.linalg.det(self.encrypt_key) + # 直接取整并转换为整数 det = int(round(det_value)) - + if det < 0: det = det % len(self.key_string) - + det_inv: int | None = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: @@ -293,7 +294,6 @@ def make_decrypt_key(self) -> np.ndarray: det_float = np.linalg.det(self.encrypt_key) inv_key = det_inv * det_float * np.linalg.inv(self.encrypt_key) return self.to_int(self.modulus(inv_key)) - def decrypt(self, text: str) -> str: """ From 5b090a31fb63105212d0722b546cf625054e771f Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:57:10 +0800 Subject: [PATCH 134/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 108 +++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 58 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index b5dc8f2a8b6b..88770d56d579 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -18,16 +18,11 @@ https://www.youtube.com/watch?v=4RhLNDqcjpA """ -# Standard library imports import string -# Third-party imports import numpy as np - -# Local application imports from maths.greatest_common_divisor import greatest_common_divisor - class HillCipher: """ Implementation of the Hill Cipher algorithm using matrix operations. @@ -148,19 +143,19 @@ def check_determinant(self) -> None: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - det_value = np.linalg.det(self.encrypt_key) - det = int(round(det_value)) - - if det < 0: - det = det % len(self.key_string) - - req_l = len(self.key_string) - if greatest_common_divisor(det, req_l) != 1: - msg = ( - f"determinant modular {req_l} of encryption key({det}) is not co prime " - f"w.r.t {req_l}.\nTry another key." - ) - raise ValueError(msg) + # 修复冗余的整数转换 + det = int(round(np.linalg.det(self.encrypt_key))) + + if det < 0: + det = det % len(self.key_string) + + req_l = len(self.key_string) + if greatest_common_divisor(det, req_l) != 1: + msg = ( + f"determinant modular {req_l} of encryption key({det}) is not co prime " + f"w.r.t {req_l}.\nTry another key." + ) + raise ValueError(msg) def process_text(self, text: str) -> str: """ @@ -188,15 +183,15 @@ def process_text(self, text: str) -> str: 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + # Handle empty input case if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: @@ -226,21 +221,21 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = self.encrypt_key.dot(batch_vec) batch_encrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted @@ -265,7 +260,7 @@ def make_decrypt_key(self) -> np.ndarray: >>> cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) - + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> cipher3 = HillCipher(key3x3) >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid @@ -274,26 +269,24 @@ def make_decrypt_key(self) -> np.ndarray: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - det_value = np.linalg.det(self.encrypt_key) - - # 直接取整并转换为整数 - det = int(round(det_value)) - - if det < 0: - det = det % len(self.key_string) - - det_inv: int | None = None - for i in range(len(self.key_string)): - if (det * i) % len(self.key_string) == 1: - det_inv = i - break - - if det_inv is None: - raise ValueError("Modular inverse does not exist for decryption key") - - det_float = np.linalg.det(self.encrypt_key) - inv_key = det_inv * det_float * np.linalg.inv(self.encrypt_key) - return self.to_int(self.modulus(inv_key)) + # 修复冗余的整数转换 + det = int(round(np.linalg.det(self.encrypt_key))) + + if det < 0: + det = det % len(self.key_string) + + det_inv: int | None = None + for i in range(len(self.key_string)): + if (det * i) % len(self.key_string) == 1: + det_inv = i + break + + if det_inv is None: + raise ValueError("Modular inverse does not exist for decryption key") + + det_float = np.linalg.det(self.encrypt_key) + inv_key = det_inv * det_float * np.linalg.inv(self.encrypt_key) + return self.to_int(self.modulus(inv_key)) def decrypt(self, text: str) -> str: """ @@ -324,22 +317,22 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = decrypt_key.dot(batch_vec) batch_decrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -352,7 +345,7 @@ def decrypt(self, text: str) -> str: def main() -> None: """ Command-line interface for Hill Cipher operations. - + Steps: 1. User inputs encryption key size 2. User inputs encryption key matrix rows @@ -365,14 +358,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i + 1}: ").split()] + row = [int(x) for x in input(f"Row {i+1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -387,21 +380,20 @@ def main() -> None: if __name__ == "__main__": import doctest - doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From 3c4e9686253323f16ceede3c0d4c6958808c6b37 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:57:35 +0000 Subject: [PATCH 135/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/hill_cipher.py | 46 ++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 88770d56d579..729654c31c0c 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -23,6 +23,7 @@ import numpy as np from maths.greatest_common_divisor import greatest_common_divisor + class HillCipher: """ Implementation of the Hill Cipher algorithm using matrix operations. @@ -145,7 +146,7 @@ def check_determinant(self) -> None: """ # 修复冗余的整数转换 det = int(round(np.linalg.det(self.encrypt_key))) - + if det < 0: det = det % len(self.key_string) @@ -183,15 +184,15 @@ def process_text(self, text: str) -> str: 'ABCC' """ chars = [char for char in text.upper() if char in self.key_string] - + # Handle empty input case if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: @@ -221,21 +222,21 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = self.encrypt_key.dot(batch_vec) batch_encrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted @@ -260,7 +261,7 @@ def make_decrypt_key(self) -> np.ndarray: >>> cipher.make_decrypt_key() array([[ 6, 25], [ 5, 26]]) - + >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> cipher3 = HillCipher(key3x3) >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid @@ -271,10 +272,10 @@ def make_decrypt_key(self) -> np.ndarray: """ # 修复冗余的整数转换 det = int(round(np.linalg.det(self.encrypt_key))) - + if det < 0: det = det % len(self.key_string) - + det_inv: int | None = None for i in range(len(self.key_string)): if (det * i) % len(self.key_string) == 1: @@ -317,22 +318,22 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): # Extract batch of characters batch = text[i : i + self.break_key] - + # Convert to numerical vector vec = [self.replace_letters(char) for char in batch] batch_vec = np.array([vec]).T - + # Matrix multiplication and mod 36 product = decrypt_key.dot(batch_vec) batch_decrypted = self.modulus(product).T.tolist()[0] - + # Convert back to characters decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted @@ -345,7 +346,7 @@ def decrypt(self, text: str) -> str: def main() -> None: """ Command-line interface for Hill Cipher operations. - + Steps: 1. User inputs encryption key size 2. User inputs encryption key matrix rows @@ -358,14 +359,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i+1}: ").split()] + row = [int(x) for x in input(f"Row {i + 1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -380,20 +381,21 @@ def main() -> None: if __name__ == "__main__": import doctest + doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From 17555671efee4593d812308c68f1cca250807435 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 02:59:33 +0800 Subject: [PATCH 136/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 729654c31c0c..814db7199dd1 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -21,6 +21,7 @@ import string import numpy as np + from maths.greatest_common_divisor import greatest_common_divisor @@ -144,8 +145,8 @@ def check_determinant(self) -> None: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - # 修复冗余的整数转换 - det = int(round(np.linalg.det(self.encrypt_key))) + + det = round(np.linalg.det(self.encrypt_key)) if det < 0: det = det % len(self.key_string) @@ -270,8 +271,8 @@ def make_decrypt_key(self) -> np.ndarray: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - # 修复冗余的整数转换 - det = int(round(np.linalg.det(self.encrypt_key))) + + det = round(np.linalg.det(self.encrypt_key)) if det < 0: det = det % len(self.key_string) From c76232621c83c136d25af4dd352b2fe3bb606c2e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:59:56 +0000 Subject: [PATCH 137/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/hill_cipher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 814db7199dd1..610b34b88ff7 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -145,7 +145,7 @@ def check_determinant(self) -> None: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - + det = round(np.linalg.det(self.encrypt_key)) if det < 0: @@ -271,7 +271,7 @@ def make_decrypt_key(self) -> np.ndarray: ValueError: determinant modular 36 of encryption key(0) is not co prime w.r.t 36. Try another key. """ - + det = round(np.linalg.det(self.encrypt_key)) if det < 0: From fc7451f62b2d74d81f1954f558c12f7d05dbcd11 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 14:07:08 +0800 Subject: [PATCH 138/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 338 ++++++++++++----------------------------- 1 file changed, 95 insertions(+), 243 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 610b34b88ff7..46ea1b082f63 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -1,27 +1,13 @@ """ -Hill Cipher Implementation +Hill Cipher Implementation with Exact Integer Arithmetic -This module provides a complete implementation of the Hill Cipher algorithm, -which uses linear algebra techniques for text encryption and decryption. -The cipher supports alphanumeric characters (A-Z, 0-9) and uses modular -arithmetic with base 36. - -Classes: - HillCipher: Implements the Hill Cipher encryption and decryption operations. - -Functions: - main: Command-line interface for the Hill Cipher operations. - -References: - https://apprendre-en-ligne.net/crypto/hill/Hillciph.pdf - https://www.youtube.com/watch?v=kfmNeskzs2o - https://www.youtube.com/watch?v=4RhLNDqcjpA +This implementation uses integer-only calculations to avoid floating-point errors +and ensure cryptographic correctness. All matrix operations are performed using +exact integer arithmetic. """ import string - import numpy as np - from maths.greatest_common_divisor import greatest_common_divisor @@ -32,23 +18,12 @@ class HillCipher: Attributes: key_string (str): String of valid characters (A-Z and 0-9) modulus (function): Vectorized function for mod 36 operation - to_int (function): Vectorized rounding function encrypt_key (np.ndarray): Encryption key matrix break_key (int): Size of the encryption key matrix (N x N) - - Methods: - replace_letters: Convert character to numerical value - replace_digits: Convert numerical value to character - check_determinant: Validate encryption key determinant - process_text: Prepare text for encryption/decryption - encrypt: Encrypt plaintext using Hill Cipher - make_decrypt_key: Compute decryption key matrix - decrypt: Decrypt ciphertext using Hill Cipher """ key_string = string.ascii_uppercase + string.digits modulus = np.vectorize(lambda x: x % 36) - to_int = np.vectorize(round) def __init__(self, encrypt_key: np.ndarray) -> None: """ @@ -59,64 +34,50 @@ def __init__(self, encrypt_key: np.ndarray) -> None: Raises: ValueError: If encryption key is invalid - - Examples: - >>> key = np.array([[2, 5], [1, 6]]) - >>> cipher = HillCipher(key) - >>> cipher.break_key - 2 """ - self.encrypt_key = self.modulus(encrypt_key) + self.encrypt_key = self.modulus(encrypt_key).astype(int) self.check_determinant() self.break_key = encrypt_key.shape[0] def replace_letters(self, letter: str) -> int: """ Convert character to its numerical equivalent. - - Args: - letter: Character to convert (A-Z or 0-9) - - Returns: - Numerical value (0-35) - - Examples: - >>> key = np.array([[2, 5], [1, 6]]) - >>> cipher = HillCipher(key) - >>> cipher.replace_letters('A') - 0 - >>> cipher.replace_letters('Z') - 25 - >>> cipher.replace_letters('0') - 26 - >>> cipher.replace_letters('9') - 35 """ return self.key_string.index(letter) def replace_digits(self, num: int) -> str: """ Convert numerical value to its character equivalent. - - Args: - num: Numerical value (0-35) - - Returns: - Character equivalent (A-Z or 0-9) - - Examples: - >>> key = np.array([[2, 5], [1, 6]]) - >>> cipher = HillCipher(key) - >>> cipher.replace_digits(0) - 'A' - >>> cipher.replace_digits(25) - 'Z' - >>> cipher.replace_digits(26) - '0' - >>> cipher.replace_digits(35) - '9' """ - return self.key_string[round(num)] + return self.key_string[num] + def integer_determinant(self, matrix: np.ndarray) -> int: + """ + Calculate determinant of an integer matrix using exact arithmetic. + """ + n = matrix.shape[0] + + # Base case for 1x1 matrix + if n == 1: + return matrix[0, 0] + + # Base case for 2x2 matrix + if n == 2: + return matrix[0, 0] * matrix[1, 1] - matrix[0, 1] * matrix[1, 0] + + det = 0 + for j in range(n): + # Create minor matrix by removing first row and j-th column + minor = matrix[1:, :] + minor = np.delete(minor, j, axis=1) + + # Recursively calculate determinant of minor + minor_det = self.integer_determinant(minor) + + # Calculate cofactor with sign + sign = (-1) ** j + det += sign * matrix[0, j] * minor_det + + return det def check_determinant(self) -> None: """ @@ -126,119 +87,54 @@ def check_determinant(self) -> None: Raises: ValueError: If determinant is not coprime with 36 - - Examples: - >>> key = np.array([[2, 5], [1, 6]]) - >>> cipher = HillCipher(key) # Valid key - - >>> invalid_key = np.array([[2, 2], [1, 1]]) - >>> HillCipher(invalid_key) # Determinant 0 - Traceback (most recent call last): - ... - ValueError: determinant modular 36 of encryption key(0) is not co prime - w.r.t 36. Try another key. - - >>> invalid_key2 = np.array([[4, 2], [6, 3]]) - >>> HillCipher(invalid_key2) # Determinant 0 - Traceback (most recent call last): - ... - ValueError: determinant modular 36 of encryption key(0) is not co prime - w.r.t 36. Try another key. """ + det = self.integer_determinant(self.encrypt_key) + + # Ensure positive modulo value + det_mod = det % len(self.key_string) + if det_mod == 0: + det_mod = len(self.key_string) - det = round(np.linalg.det(self.encrypt_key)) - - if det < 0: - det = det % len(self.key_string) - - req_l = len(self.key_string) - if greatest_common_divisor(det, req_l) != 1: + if greatest_common_divisor(det_mod, len(self.key_string)) != 1: msg = ( - f"determinant modular {req_l} of encryption key({det}) is not co prime " - f"w.r.t {req_l}.\nTry another key." + f"determinant modular {len(self.key_string)} of encryption key({det}) " + f"is not co prime w.r.t {len(self.key_string)}. Try another key." ) raise ValueError(msg) def process_text(self, text: str) -> str: """ - Prepare text for encryption/decryption by: - 1. Converting to uppercase - 2. Removing invalid characters - 3. Padding with last character to make length multiple of key size - - Args: - text: Text to process - - Returns: - Processed text ready for encryption/decryption - - Examples: - >>> key = np.array([[2, 5], [1, 6]]) - >>> cipher = HillCipher(key) - >>> cipher.process_text('Test!123') - 'TEST123' - >>> cipher.process_text('hello') - 'HELLOO' - >>> cipher.process_text('a') - 'AA' - >>> cipher.process_text('abc') - 'ABCC' + Prepare text for encryption/decryption. """ chars = [char for char in text.upper() if char in self.key_string] - - # Handle empty input case + if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: """ Encrypt plaintext using Hill Cipher. - - Args: - text: Plaintext to encrypt - - Returns: - Encrypted ciphertext - - Examples: - >>> key = np.array([[2, 5], [1, 6]]) - >>> cipher = HillCipher(key) - >>> cipher.encrypt('Test') - '4Q6J' - >>> cipher.encrypt('Hello World') - '85FF00V4ZAH8' - >>> cipher.encrypt('ABC') - 'A0K' - >>> cipher.encrypt('123') - '0RZ' - >>> cipher.encrypt('a') - 'PP' """ text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" - for i in range(0, len(text) - self.break_key + 1, self.break_key): - # Extract batch of characters - batch = text[i : i + self.break_key] - - # Convert to numerical vector + for i in range(0, len(text), self.break_key): + batch = text[i:i+self.break_key] vec = [self.replace_letters(char) for char in batch] - batch_vec = np.array([vec]).T - - # Matrix multiplication and mod 36 - product = self.encrypt_key.dot(batch_vec) - batch_encrypted = self.modulus(product).T.tolist()[0] - - # Convert back to characters + batch_vec = np.array(vec).reshape(-1, 1) + + product = self.encrypt_key @ batch_vec + batch_encrypted = self.modulus(product).flatten().astype(int) + encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted ) @@ -249,93 +145,56 @@ def encrypt(self, text: str) -> str: def make_decrypt_key(self) -> np.ndarray: """ Compute decryption key matrix from encryption key. - - Returns: - Decryption key matrix - - Raises: - ValueError: If modular inverse doesn't exist - - Examples: - >>> key = np.array([[2, 5], [1, 6]]) - >>> cipher = HillCipher(key) - >>> cipher.make_decrypt_key() - array([[ 6, 25], - [ 5, 26]]) - - >>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]]) - >>> cipher3 = HillCipher(key3x3) - >>> cipher3.make_decrypt_key() # Determinant 0 should be invalid - Traceback (most recent call last): - ... - ValueError: determinant modular 36 of encryption key(0) is not co prime - w.r.t 36. Try another key. """ - - det = round(np.linalg.det(self.encrypt_key)) - - if det < 0: - det = det % len(self.key_string) - - det_inv: int | None = None - for i in range(len(self.key_string)): - if (det * i) % len(self.key_string) == 1: + n = self.break_key + det = self.integer_determinant(self.encrypt_key) + modulus = len(self.key_string) + + # Find modular inverse of determinant + det_mod = det % modulus + det_inv = None + for i in range(1, modulus): + if (det_mod * i) % modulus == 1: det_inv = i break if det_inv is None: - raise ValueError("Modular inverse does not exist for decryption key") - - det_float = np.linalg.det(self.encrypt_key) - inv_key = det_inv * det_float * np.linalg.inv(self.encrypt_key) - return self.to_int(self.modulus(inv_key)) + raise ValueError("Modular inverse does not exist") + + # Compute adjugate matrix + adjugate = np.zeros((n, n), dtype=int) + + for i in range(n): + for j in range(n): + minor = np.delete(self.encrypt_key, i, axis=0) + minor = np.delete(minor, j, axis=1) + minor_det = self.integer_determinant(minor) + sign = (-1) ** (i + j) + adjugate[j, i] = sign * minor_det # Transposed assignment + + # Apply modular inverse and mod operation + inv_key = (det_inv * adjugate) % modulus + return inv_key def decrypt(self, text: str) -> str: """ Decrypt ciphertext using Hill Cipher. - - Args: - text: Ciphertext to decrypt - - Returns: - Decrypted plaintext - - Examples: - >>> key = np.array([[2, 5], [1, 6]]) - >>> cipher = HillCipher(key) - >>> cipher.decrypt('4Q6J') - 'TEST' - >>> cipher.decrypt('85FF00V4ZAH8') - 'HELLOWORLDD' - >>> cipher.decrypt('A0K') - 'ABCC' - >>> cipher.decrypt('0RZ') - '1233' - >>> cipher.decrypt('PP') - 'AA' - >>> cipher.decrypt('') - '' """ text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" - for i in range(0, len(text) - self.break_key + 1, self.break_key): - # Extract batch of characters - batch = text[i : i + self.break_key] - - # Convert to numerical vector + for i in range(0, len(text), self.break_key): + batch = text[i:i+self.break_key] vec = [self.replace_letters(char) for char in batch] - batch_vec = np.array([vec]).T - - # Matrix multiplication and mod 36 - product = decrypt_key.dot(batch_vec) - batch_decrypted = self.modulus(product).T.tolist()[0] - - # Convert back to characters + batch_vec = np.array(vec).reshape(-1, 1) + + product = decrypt_key @ batch_vec + batch_decrypted = self.modulus(product).flatten().astype(int) + decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted ) @@ -344,30 +203,24 @@ def decrypt(self, text: str) -> str: return decrypted + def main() -> None: """ Command-line interface for Hill Cipher operations. - - Steps: - 1. User inputs encryption key size - 2. User inputs encryption key matrix rows - 3. User chooses encryption or decryption - 4. User inputs text to process - 5. Program outputs result """ n = int(input("Enter the order of the encryption key: ")) hill_matrix = [] print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i + 1}: ").split()] + row = [int(x) for x in input(f"Row {i+1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -382,21 +235,20 @@ def main() -> None: if __name__ == "__main__": import doctest - doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From a9f32301156b61a9b30fb58c3fc12b27f96f6758 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Jul 2025 06:07:31 +0000 Subject: [PATCH 139/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/hill_cipher.py | 55 +++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 46ea1b082f63..1b1322a73035 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -50,33 +50,34 @@ def replace_digits(self, num: int) -> str: Convert numerical value to its character equivalent. """ return self.key_string[num] + def integer_determinant(self, matrix: np.ndarray) -> int: """ Calculate determinant of an integer matrix using exact arithmetic. """ n = matrix.shape[0] - + # Base case for 1x1 matrix if n == 1: return matrix[0, 0] - + # Base case for 2x2 matrix if n == 2: return matrix[0, 0] * matrix[1, 1] - matrix[0, 1] * matrix[1, 0] - + det = 0 for j in range(n): # Create minor matrix by removing first row and j-th column minor = matrix[1:, :] minor = np.delete(minor, j, axis=1) - + # Recursively calculate determinant of minor minor_det = self.integer_determinant(minor) - + # Calculate cofactor with sign sign = (-1) ** j det += sign * matrix[0, j] * minor_det - + return det def check_determinant(self) -> None: @@ -89,7 +90,7 @@ def check_determinant(self) -> None: ValueError: If determinant is not coprime with 36 """ det = self.integer_determinant(self.encrypt_key) - + # Ensure positive modulo value det_mod = det % len(self.key_string) if det_mod == 0: @@ -107,14 +108,14 @@ def process_text(self, text: str) -> str: Prepare text for encryption/decryption. """ chars = [char for char in text.upper() if char in self.key_string] - + if not chars: return "" - + last = chars[-1] while len(chars) % self.break_key != 0: chars.append(last) - + return "".join(chars) def encrypt(self, text: str) -> str: @@ -124,17 +125,17 @@ def encrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + encrypted = "" for i in range(0, len(text), self.break_key): - batch = text[i:i+self.break_key] + batch = text[i : i + self.break_key] vec = [self.replace_letters(char) for char in batch] batch_vec = np.array(vec).reshape(-1, 1) - + product = self.encrypt_key @ batch_vec batch_encrypted = self.modulus(product).flatten().astype(int) - + encrypted_batch = "".join( self.replace_digits(num) for num in batch_encrypted ) @@ -149,7 +150,7 @@ def make_decrypt_key(self) -> np.ndarray: n = self.break_key det = self.integer_determinant(self.encrypt_key) modulus = len(self.key_string) - + # Find modular inverse of determinant det_mod = det % modulus det_inv = None @@ -163,7 +164,7 @@ def make_decrypt_key(self) -> np.ndarray: # Compute adjugate matrix adjugate = np.zeros((n, n), dtype=int) - + for i in range(n): for j in range(n): minor = np.delete(self.encrypt_key, i, axis=0) @@ -183,18 +184,18 @@ def decrypt(self, text: str) -> str: text = self.process_text(text.upper()) if not text: return "" - + decrypt_key = self.make_decrypt_key() decrypted = "" for i in range(0, len(text), self.break_key): - batch = text[i:i+self.break_key] + batch = text[i : i + self.break_key] vec = [self.replace_letters(char) for char in batch] batch_vec = np.array(vec).reshape(-1, 1) - + product = decrypt_key @ batch_vec batch_decrypted = self.modulus(product).flatten().astype(int) - + decrypted_batch = "".join( self.replace_digits(num) for num in batch_decrypted ) @@ -203,7 +204,6 @@ def decrypt(self, text: str) -> str: return decrypted - def main() -> None: """ Command-line interface for Hill Cipher operations. @@ -213,14 +213,14 @@ def main() -> None: print("Enter each row of the encryption key with space separated integers") for i in range(n): - row = [int(x) for x in input(f"Row {i+1}: ").split()] + row = [int(x) for x in input(f"Row {i + 1}: ").split()] hill_matrix.append(row) hc = HillCipher(np.array(hill_matrix)) print("\nWould you like to encrypt or decrypt some text?") option = input("1. Encrypt\n2. Decrypt\nEnter choice (1/2): ") - + if option == "1": text = input("\nEnter text to encrypt: ") print("\nEncrypted text:") @@ -235,20 +235,21 @@ def main() -> None: if __name__ == "__main__": import doctest + doctest.testmod() - + print("\nRunning sample tests...") key = np.array([[2, 5], [1, 6]]) cipher = HillCipher(key) - + # Test encryption/decryption round trip plaintext = "HELLO123" encrypted = cipher.encrypt(plaintext) decrypted = cipher.decrypt(encrypted) - + print(f"\nOriginal text: {plaintext}") print(f"Encrypted text: {encrypted}") print(f"Decrypted text: {decrypted}") - + # Run CLI interface main() From 90e39fb39e3b62fbb1b48a0b15891b080f643a57 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 14:08:57 +0800 Subject: [PATCH 140/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 1b1322a73035..f6369104a99b 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -7,6 +7,7 @@ """ import string + import numpy as np from maths.greatest_common_divisor import greatest_common_divisor From e9b44121ad5153b6476a38f0a6574d9feb13f45d Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 14:09:53 +0800 Subject: [PATCH 141/156] Update hill_cipher.py --- ciphers/hill_cipher.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index f6369104a99b..ffeef6302329 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -9,6 +9,7 @@ import string import numpy as np + from maths.greatest_common_divisor import greatest_common_divisor From d1730fcc2f88f4467331823279bd9546ba96910f Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 15:13:25 +0800 Subject: [PATCH 142/156] Update pyproject.toml --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index afca7514fb00..233a0ae7c513 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,9 @@ docs = [ "myst-parser>=4", "sphinx-autoapi>=3.4", "sphinx-pyproject>=0.3", + "furo==2024.1.29", + "sphinx-copybutton>=0.5", + "sphinx-design>=0.5" ] euler-validate = [ "httpx>=0.28.1", @@ -236,7 +239,7 @@ extensions = [ "myst_parser", ] html_static_path = [ "_static" ] -html_theme = "alabaster" +html_theme = "furo" myst_enable_extensions = [ "amsmath", "attrs_inline", From d0ea2166f7e90b39107b07d626b1741f2258a1a4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Jul 2025 07:13:47 +0000 Subject: [PATCH 143/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 233a0ae7c513..e60174a66b1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,12 +38,12 @@ test = [ ] docs = [ + "furo==2024.1.29", "myst-parser>=4", "sphinx-autoapi>=3.4", + "sphinx-copybutton>=0.5", + "sphinx-design>=0.5", "sphinx-pyproject>=0.3", - "furo==2024.1.29", - "sphinx-copybutton>=0.5", - "sphinx-design>=0.5" ] euler-validate = [ "httpx>=0.28.1", From 6c75ea1a42365d6ebbc2a77a87fa4ffb6dcdc662 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 15:36:04 +0800 Subject: [PATCH 144/156] Update send_file.py --- file_transfer/send_file.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/file_transfer/send_file.py b/file_transfer/send_file.py index 1c56e48f47a1..01971d1163ff 100644 --- a/file_transfer/send_file.py +++ b/file_transfer/send_file.py @@ -1,5 +1,7 @@ +import socket + def send_file(filename: str = "mytext.txt", testing: bool = False) -> None: - import socket + port = 12312 # Reserve a port for your service. sock = socket.socket() # Create a socket object From 91b2f25e681a95db8d6728dc0ed45c6e25a71396 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Jul 2025 07:36:26 +0000 Subject: [PATCH 145/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- file_transfer/send_file.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/file_transfer/send_file.py b/file_transfer/send_file.py index 01971d1163ff..b6fb11db4e5d 100644 --- a/file_transfer/send_file.py +++ b/file_transfer/send_file.py @@ -1,8 +1,7 @@ import socket -def send_file(filename: str = "mytext.txt", testing: bool = False) -> None: - +def send_file(filename: str = "mytext.txt", testing: bool = False) -> None: port = 12312 # Reserve a port for your service. sock = socket.socket() # Create a socket object host = socket.gethostname() # Get local machine name From 6ac7dd88994b0d64be28ee798bf2024f7cbcbcf9 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 15:40:34 +0800 Subject: [PATCH 146/156] rallback pyproject.toml --- pyproject.toml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e60174a66b1a..b8ad48291176 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,12 +38,9 @@ test = [ ] docs = [ - "furo==2024.1.29", - "myst-parser>=4", - "sphinx-autoapi>=3.4", - "sphinx-copybutton>=0.5", - "sphinx-design>=0.5", - "sphinx-pyproject>=0.3", + "myst-parser>=4", + "sphinx-autoapi>=3.4", + "sphinx-pyproject>=0.3" ] euler-validate = [ "httpx>=0.28.1", @@ -239,7 +236,7 @@ extensions = [ "myst_parser", ] html_static_path = [ "_static" ] -html_theme = "furo" +html_theme = "alabaster" myst_enable_extensions = [ "amsmath", "attrs_inline", From b7a95857ec0b9233cc77d70879e44df06209ce4d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Jul 2025 07:40:56 +0000 Subject: [PATCH 147/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b8ad48291176..afca7514fb00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,9 +38,9 @@ test = [ ] docs = [ - "myst-parser>=4", - "sphinx-autoapi>=3.4", - "sphinx-pyproject>=0.3" + "myst-parser>=4", + "sphinx-autoapi>=3.4", + "sphinx-pyproject>=0.3", ] euler-validate = [ "httpx>=0.28.1", From ea7703183aa32e8240ad2b6d7d737691f276287c Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 15:48:18 +0800 Subject: [PATCH 148/156] Update average_median.py --- maths/average_median.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/average_median.py b/maths/average_median.py index f24e525736b3..a5d06c693dd8 100644 --- a/maths/average_median.py +++ b/maths/average_median.py @@ -1,5 +1,6 @@ from __future__ import annotations +import doctest def median(nums: list) -> int | float: """ @@ -32,7 +33,6 @@ def median(nums: list) -> int | float: def main(): - import doctest doctest.testmod() From 5d1b795b3afc693f580427f3a0dca22fcc913b84 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Jul 2025 07:48:40 +0000 Subject: [PATCH 149/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- maths/average_median.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/average_median.py b/maths/average_median.py index a5d06c693dd8..207a92dc4f3f 100644 --- a/maths/average_median.py +++ b/maths/average_median.py @@ -2,6 +2,7 @@ import doctest + def median(nums: list) -> int | float: """ Find median of a list of numbers. @@ -33,7 +34,6 @@ def median(nums: list) -> int | float: def main(): - doctest.testmod() From 3b0d81ff9ea17762bfa3d9c8e8bd1bed171d3ea7 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 16:18:40 +0800 Subject: [PATCH 150/156] Update singly_linked_list.py --- data_structures/linked_list/singly_linked_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 2c6713a47ad9..e45f8bb605d9 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -2,6 +2,7 @@ from collections.abc import Iterator from dataclasses import dataclass +from doctest import testmod from typing import Any @@ -492,7 +493,7 @@ def test_singly_linked_list_2() -> None: def main(): - from doctest import testmod + testmod() From d857ae2f08e9a3003bad3e33e1a05e299e223f52 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Jul 2025 08:19:02 +0000 Subject: [PATCH 151/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/linked_list/singly_linked_list.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index e45f8bb605d9..08b737445f2a 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -493,8 +493,6 @@ def test_singly_linked_list_2() -> None: def main(): - - testmod() linked_list = LinkedList() From aa638030fa3cf9f101af119bb5a10c12b86119ab Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 16:33:59 +0800 Subject: [PATCH 152/156] Update current_stock_price.py --- web_programming/current_stock_price.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py index 16b0b6772a9c..45f8142e9436 100644 --- a/web_programming/current_stock_price.py +++ b/web_programming/current_stock_price.py @@ -1,13 +1,7 @@ -# /// script -# requires-python = ">=3.13" -# dependencies = [ -# "beautifulsoup4", -# "httpx", -# ] -# /// - -import httpx +import requests + from bs4 import BeautifulSoup +from doctest import testmod """ Get the HTML code of finance yahoo and select the current qsp-price @@ -28,20 +22,22 @@ def stock_price(symbol: str = "AAPL") -> str: True """ url = f"https://finance.yahoo.com/quote/{symbol}?p={symbol}" - yahoo_finance_source = httpx.get( - url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10, follow_redirects=True - ).text + try: + yahoo_finance_source = requests.get( + url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10 + ).text + except requests.exceptions.RequestException: + return '- ' + soup = BeautifulSoup(yahoo_finance_source, "html.parser") if specific_fin_streamer_tag := soup.find("span", {"data-testid": "qsp-price"}): return specific_fin_streamer_tag.get_text() - return "No tag with the specified data-testid attribute found." + return '- ' # Search for the symbol at https://finance.yahoo.com/lookup if __name__ == "__main__": - from doctest import testmod - testmod() for symbol in "AAPL AMZN IBM GOOG MSFT ORCL".split(): From 3eed96ca5511012ea35a7b9c6268a2e1b7794db7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Jul 2025 08:34:22 +0000 Subject: [PATCH 153/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- web_programming/current_stock_price.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py index 45f8142e9436..271caaea4cda 100644 --- a/web_programming/current_stock_price.py +++ b/web_programming/current_stock_price.py @@ -27,13 +27,13 @@ def stock_price(symbol: str = "AAPL") -> str: url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10 ).text except requests.exceptions.RequestException: - return '- ' + return "- " soup = BeautifulSoup(yahoo_finance_source, "html.parser") if specific_fin_streamer_tag := soup.find("span", {"data-testid": "qsp-price"}): return specific_fin_streamer_tag.get_text() - return '- ' + return "- " # Search for the symbol at https://finance.yahoo.com/lookup From 21381f3c3d06a6e3b7f7270ee193b17082a32524 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 16:36:17 +0800 Subject: [PATCH 154/156] Update current_stock_price.py --- web_programming/current_stock_price.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py index 271caaea4cda..6f2db87818e9 100644 --- a/web_programming/current_stock_price.py +++ b/web_programming/current_stock_price.py @@ -1,7 +1,6 @@ +from doctest import testmod import requests - from bs4 import BeautifulSoup -from doctest import testmod """ Get the HTML code of finance yahoo and select the current qsp-price From 1c157579ca914b445132c16c742a0155ff43bc4c Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 5 Jul 2025 16:39:13 +0800 Subject: [PATCH 155/156] Update current_stock_price.py --- web_programming/current_stock_price.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py index 6f2db87818e9..a76c3742bdb1 100644 --- a/web_programming/current_stock_price.py +++ b/web_programming/current_stock_price.py @@ -1,7 +1,6 @@ -from doctest import testmod import requests from bs4 import BeautifulSoup - +from doctest import testmod """ Get the HTML code of finance yahoo and select the current qsp-price Current AAPL stock price is 228.43 From 2040c6098b9c72445f01df877a1a700957c7dc9e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Jul 2025 08:39:36 +0000 Subject: [PATCH 156/156] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- web_programming/current_stock_price.py | 1 + 1 file changed, 1 insertion(+) diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py index a76c3742bdb1..9aa2048193f2 100644 --- a/web_programming/current_stock_price.py +++ b/web_programming/current_stock_price.py @@ -1,6 +1,7 @@ import requests from bs4 import BeautifulSoup from doctest import testmod + """ Get the HTML code of finance yahoo and select the current qsp-price Current AAPL stock price is 228.43