Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data_structures/hashing/hash_table_with_linked_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def _set_value(self, key, data):
self.values[key] = deque([]) if self.values[key] is None else self.values[key]
self.values[key] = deque() if self.values[key] is None else self.values[key]
self.values[key].appendleft(data)
self._keys[key] = self.values[key]

Expand Down
29 changes: 13 additions & 16 deletions graphs/check_bipatrite.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
from collections import defaultdict, deque
from collections.abc import Hashable, Iterable, Mapping


def is_bipartite_dfs(graph: dict[int, list[int]]) -> bool:
def is_bipartite_dfs(graph: Mapping[Hashable, Iterable[Hashable]]) -> bool:
"""
Check if a graph is bipartite using depth-first search (DFS).
Args:
`graph`: Adjacency list representing the graph.
`graph`: Mapping of nodes to their neighbors. Nodes must be hashable.
Returns:
``True`` if bipartite, ``False`` otherwise.
Checks if the graph can be divided into two sets of vertices, such that no two
vertices within the same set are connected by an edge.
vertices within the same set are connected by an edge. Neighbor nodes that do
not appear as keys are treated as isolated nodes with no outgoing edges.
Examples:
Expand All @@ -33,7 +35,6 @@ def is_bipartite_dfs(graph: dict[int, list[int]]) -> bool:
>>> is_bipartite_dfs({7: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]})
False
>>> # FIXME: This test should fails with KeyError: 4.
>>> is_bipartite_dfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]})
False
>>> is_bipartite_dfs({0: [-1, 3], 1: [0, -2]})
Expand All @@ -43,8 +44,6 @@ def is_bipartite_dfs(graph: dict[int, list[int]]) -> bool:
>>> is_bipartite_dfs({0.9: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]})
True
>>> # FIXME: This test should fails with
>>> # TypeError: list indices must be integers or...
>>> is_bipartite_dfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]})
True
>>> is_bipartite_dfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]})
Expand All @@ -53,7 +52,7 @@ def is_bipartite_dfs(graph: dict[int, list[int]]) -> bool:
True
"""

def depth_first_search(node: int, color: int) -> bool:
def depth_first_search(node: Hashable, color: int) -> bool:
"""
Perform Depth-First Search (DFS) on the graph starting from a node.
Expand All @@ -74,25 +73,26 @@ def depth_first_search(node: int, color: int) -> bool:
return False
return visited[node] == color

visited: defaultdict[int, int] = defaultdict(lambda: -1)
visited: defaultdict[Hashable, int] = defaultdict(lambda: -1)
for node in graph:
if visited[node] == -1 and not depth_first_search(node, 0):
return False
return True


def is_bipartite_bfs(graph: dict[int, list[int]]) -> bool:
def is_bipartite_bfs(graph: Mapping[Hashable, Iterable[Hashable]]) -> bool:
"""
Check if a graph is bipartite using a breadth-first search (BFS).
Args:
`graph`: Adjacency list representing the graph.
`graph`: Mapping of nodes to their neighbors. Nodes must be hashable.
Returns:
``True`` if bipartite, ``False`` otherwise.
Check if the graph can be divided into two sets of vertices, such that no two
vertices within the same set are connected by an edge.
vertices within the same set are connected by an edge. Neighbor nodes that do
not appear as keys are treated as isolated nodes with no outgoing edges.
Examples:
Expand All @@ -113,7 +113,6 @@ def is_bipartite_bfs(graph: dict[int, list[int]]) -> bool:
>>> is_bipartite_bfs({7: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]})
False
>>> # FIXME: This test should fails with KeyError: 4.
>>> is_bipartite_bfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]})
False
>>> is_bipartite_bfs({0: [-1, 3], 1: [0, -2]})
Expand All @@ -123,19 +122,17 @@ def is_bipartite_bfs(graph: dict[int, list[int]]) -> bool:
>>> is_bipartite_bfs({0.9: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]})
True
>>> # FIXME: This test should fails with
>>> # TypeError: list indices must be integers or...
>>> is_bipartite_bfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]})
True
>>> is_bipartite_bfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]})
True
>>> is_bipartite_bfs({0: ["b", "d"], 1: ["a", "c"], 2: ["b", "d"], 3: ["a", "c"]})
True
"""
visited: defaultdict[int, int] = defaultdict(lambda: -1)
visited: defaultdict[Hashable, int] = defaultdict(lambda: -1)
for node in graph:
if visited[node] == -1:
queue: deque[int] = deque()
queue: deque[Hashable] = deque()
queue.append(node)
visited[node] = 0
while queue:
Expand Down
12 changes: 4 additions & 8 deletions machine_learning/linear_discriminant_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
from math import log
from os import name, system
from random import gauss, seed
from typing import TypeVar


# Make a training dataset drawn from a gaussian distribution
Expand Down Expand Up @@ -249,16 +248,13 @@ def accuracy(actual_y: list, predicted_y: list) -> float:
return (correct / len(actual_y)) * 100


num = TypeVar("num")


def valid_input(
input_type: Callable[[object], num], # Usually float or int
def valid_input[T](
input_type: Callable[[object], T], # Usually float or int
input_msg: str,
err_msg: str,
condition: Callable[[num], bool] = lambda _: True,
condition: Callable[[T], bool] = lambda _: True,
default: str | None = None,
) -> num:
) -> T:
"""
Ask for user value and validate that it fulfill a condition.

Expand Down
7 changes: 2 additions & 5 deletions searches/jump_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,14 @@

import math
from collections.abc import Sequence
from typing import Any, Protocol, TypeVar
from typing import Any, Protocol


class Comparable(Protocol):
def __lt__(self, other: Any, /) -> bool: ...


T = TypeVar("T", bound=Comparable)


def jump_search(arr: Sequence[T], item: T) -> int:
def jump_search[T: Comparable](arr: Sequence[T], item: T) -> int:
"""
Python implementation of the jump search algorithm.
Return the index if the `item` is found, otherwise return -1.
Expand Down
77 changes: 75 additions & 2 deletions searches/linear_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
def linear_search(sequence: list, target: int) -> int:
"""A pure Python implementation of a linear search algorithm

Linear search iterates through a collection sequentially until it finds the
target element or reaches the end of the collection. It works on both sorted
and unsorted collections.

:param sequence: a collection with comparable items (as sorted items not required
in Linear Search)
:param target: item value to search
Expand All @@ -26,6 +30,42 @@ def linear_search(sequence: list, target: int) -> int:
1
>>> linear_search([0, 5, 7, 10, 15], 6)
-1

>>> linear_search([], 5)
-1

>>> linear_search([42], 42)
0

>>> linear_search([42], 99)
-1

>>> linear_search([3, 1, 4, 1, 5, 9, 2, 6], 1)
1

>>> linear_search([3, 1, 4, 1, 5, 9, 2, 6], 9)
5

>>> linear_search([10, 20, 30, 40, 50], 30)
2

>>> linear_search([5, 5, 5, 5], 5)
0

>>> linear_search([-5, -2, 0, 3, 7], -5)
0

>>> linear_search([-5, -2, 0, 3, 7], 0)
2

>>> linear_search([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10)
9

>>> linear_search([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 1)
0

>>> linear_search([100, 200, 300, 400], 250)
-1
"""
for index, item in enumerate(sequence):
if item == target:
Expand All @@ -37,10 +77,13 @@ def rec_linear_search(sequence: list, low: int, high: int, target: int) -> int:
"""
A pure Python implementation of a recursive linear search algorithm

This is the recursive variant of linear search. It searches from both bounds
(low and high) converging toward the middle, checking both ends simultaneously.

:param sequence: a collection with comparable items (as sorted items not required
in Linear Search)
:param low: Lower bound of the array
:param high: Higher bound of the array
:param low: Lower bound of the array (starting index)
:param high: Higher bound of the array (ending index)
:param target: The element to be found
:return: Index of the key or -1 if key not found

Expand All @@ -53,6 +96,36 @@ def rec_linear_search(sequence: list, low: int, high: int, target: int) -> int:
1
>>> rec_linear_search([0, 30, 500, 100, 700], 0, 4, -6)
-1

>>> rec_linear_search([42], 0, 0, 42)
0

>>> rec_linear_search([42], 0, 0, 99)
-1

>>> rec_linear_search([1, 2, 3, 4, 5], 0, 4, 1)
0

>>> rec_linear_search([1, 2, 3, 4, 5], 0, 4, 5)
4

>>> rec_linear_search([1, 2, 3, 4, 5], 0, 4, 3)
2

>>> rec_linear_search([10, 20, 30, 40, 50, 60], 0, 5, 40)
3

>>> rec_linear_search([-5, -2, 0, 3, 7], 0, 4, -5)
0

>>> rec_linear_search([-5, -2, 0, 3, 7], 0, 4, 7)
4

>>> rec_linear_search([5, 5, 5, 5, 5], 0, 4, 5)
0

>>> rec_linear_search([1, 2, 3, 4, 5], 0, 4, 0)
-1
"""
if not (0 <= high < len(sequence) and 0 <= low < len(sequence)):
raise Exception("Invalid upper or lower bound!")
Expand Down
Loading