Merge branch 'TheAlgorithms:master' into master

This commit is contained in:
BBEK-Anand 2024-11-20 22:10:35 +05:30 committed by GitHub
commit 6047fe3098
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
330 changed files with 3449 additions and 1447 deletions

View File

@ -1,5 +1,5 @@
# https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/README.md
ARG VARIANT=3.12-bookworm
ARG VARIANT=3.13-bookworm
FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
COPY requirements.txt /tmp/pip-tmp/
RUN python3 -m pip install --upgrade pip \

View File

@ -7,7 +7,7 @@
// Update 'VARIANT' to pick a Python version: 3, 3.11, 3.10, 3.9, 3.8
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "3.12-bookworm",
"VARIANT": "3.13-bookworm",
}
},

2
.github/CODEOWNERS vendored
View File

@ -9,8 +9,6 @@
/.* @cclauss
# /arithmetic_analysis/
# /backtracking/
# /bit_manipulation/

View File

@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.12
python-version: 3.13
allow-prereleases: true
- uses: actions/cache@v4
with:
@ -20,13 +20,18 @@ jobs:
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools six wheel
python -m pip install --upgrade pip setuptools wheel
python -m pip install pytest-cov -r requirements.txt
- name: Run tests
# TODO: #8818 Re-enable quantum tests
run: pytest
--ignore=quantum/q_fourier_transform.py
--ignore=computer_vision/cnn_classification.py
--ignore=docs/conf.py
--ignore=dynamic_programming/k_means_clustering_tensorflow.py
--ignore=machine_learning/lstm/lstm_prediction.py
--ignore=neural_network/input_data.py
--ignore=project_euler/
--ignore=quantum/q_fourier_transform.py
--ignore=scripts/validate_solutions.py
--cov-report=term-missing:skip-covered
--cov=. .

50
.github/workflows/sphinx.yml vendored Normal file
View File

@ -0,0 +1,50 @@
name: sphinx
on:
# Triggers the workflow on push or pull request events but only for the "master" branch
push:
branches: ["master"]
pull_request:
branches: ["master"]
# Or manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build_docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.13
allow-prereleases: true
- run: pip install --upgrade pip
- run: pip install myst-parser sphinx-autoapi sphinx-pyproject
- uses: actions/configure-pages@v5
- run: sphinx-build -c docs . docs/_build/html
- uses: actions/upload-pages-artifact@v3
with:
path: docs/_build/html
deploy_docs:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
if: github.event_name != 'pull_request'
needs: build_docs
runs-on: ubuntu-latest
steps:
- uses: actions/deploy-pages@v4
id: deployment

View File

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v5.0.0
hooks:
- id: check-executables-have-shebangs
- id: check-toml
@ -11,25 +11,25 @@ repos:
- id: requirements-txt-fixer
- repo: https://github.com/MarcoGorelli/auto-walrus
rev: v0.2.2
rev: 0.3.4
hooks:
- id: auto-walrus
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4
rev: v0.7.4
hooks:
- id: ruff
- id: ruff-format
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
rev: v2.3.0
hooks:
- id: codespell
additional_dependencies:
- tomli
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "1.7.0"
rev: "v2.5.0"
hooks:
- id: pyproject-fmt
@ -42,15 +42,16 @@ repos:
pass_filenames: false
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.16
rev: v0.23
hooks:
- id: validate-pyproject
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.9.0
rev: v1.13.0
hooks:
- id: mypy
args:
- --explicit-package-bases
- --ignore-missing-imports
- --install-types # See mirrors-mypy README.md
- --non-interactive

View File

@ -77,7 +77,7 @@ pre-commit run --all-files --show-diff-on-failure
We want your work to be readable by others; therefore, we encourage you to note the following:
- Please write in Python 3.12+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will.
- Please write in Python 3.13+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will.
- Please focus hard on the naming of functions, classes, and variables. Help your reader by using __descriptive names__ that can help you to remove redundant comments.
- Single letter variable names are *old school* so please avoid them unless their life only spans a few lines.
- Expand acronyms because `gcd()` is hard to understand but `greatest_common_divisor()` is not.
@ -96,7 +96,7 @@ We want your work to be readable by others; therefore, we encourage you to note
```bash
python3 -m pip install ruff # only required the first time
ruff .
ruff check
```
- Original code submission require docstrings or comments to describe your work.

View File

@ -22,6 +22,8 @@
* [Rat In Maze](backtracking/rat_in_maze.py)
* [Sudoku](backtracking/sudoku.py)
* [Sum Of Subsets](backtracking/sum_of_subsets.py)
* [Word Break](backtracking/word_break.py)
* [Word Ladder](backtracking/word_ladder.py)
* [Word Search](backtracking/word_search.py)
## Bit Manipulation
@ -98,6 +100,7 @@
* [Elgamal Key Generator](ciphers/elgamal_key_generator.py)
* [Enigma Machine2](ciphers/enigma_machine2.py)
* [Fractionated Morse Cipher](ciphers/fractionated_morse_cipher.py)
* [Gronsfeld Cipher](ciphers/gronsfeld_cipher.py)
* [Hill Cipher](ciphers/hill_cipher.py)
* [Mixed Keyword Cypher](ciphers/mixed_keyword_cypher.py)
* [Mono Alphabetic Ciphers](ciphers/mono_alphabetic_ciphers.py)
@ -210,6 +213,7 @@
* [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py)
* [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py)
* [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.py)
* [Maximum Sum Bst](data_structures/binary_tree/maximum_sum_bst.py)
* [Merge Two Binary Trees](data_structures/binary_tree/merge_two_binary_trees.py)
* [Mirror Binary Tree](data_structures/binary_tree/mirror_binary_tree.py)
* [Non Recursive Segment Tree](data_structures/binary_tree/non_recursive_segment_tree.py)
@ -243,6 +247,15 @@
* [Min Heap](data_structures/heap/min_heap.py)
* [Randomized Heap](data_structures/heap/randomized_heap.py)
* [Skew Heap](data_structures/heap/skew_heap.py)
* Kd Tree
* [Build Kdtree](data_structures/kd_tree/build_kdtree.py)
* Example
* [Example Usage](data_structures/kd_tree/example/example_usage.py)
* [Hypercube Points](data_structures/kd_tree/example/hypercube_points.py)
* [Kd Node](data_structures/kd_tree/kd_node.py)
* [Nearest Neighbour Search](data_structures/kd_tree/nearest_neighbour_search.py)
* Tests
* [Test Kdtree](data_structures/kd_tree/tests/test_kdtree.py)
* Linked List
* [Circular Linked List](data_structures/linked_list/circular_linked_list.py)
* [Deque Doubly](data_structures/linked_list/deque_doubly.py)
@ -274,6 +287,7 @@
* [Dijkstras Two Stack Algorithm](data_structures/stacks/dijkstras_two_stack_algorithm.py)
* [Infix To Postfix Conversion](data_structures/stacks/infix_to_postfix_conversion.py)
* [Infix To Prefix Conversion](data_structures/stacks/infix_to_prefix_conversion.py)
* [Lexicographical Numbers](data_structures/stacks/lexicographical_numbers.py)
* [Next Greater Element](data_structures/stacks/next_greater_element.py)
* [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py)
* [Prefix Evaluation](data_structures/stacks/prefix_evaluation.py)
@ -282,6 +296,13 @@
* [Stack With Doubly Linked List](data_structures/stacks/stack_with_doubly_linked_list.py)
* [Stack With Singly Linked List](data_structures/stacks/stack_with_singly_linked_list.py)
* [Stock Span Problem](data_structures/stacks/stock_span_problem.py)
* Suffix Tree
* Example
* [Example Usage](data_structures/suffix_tree/example/example_usage.py)
* [Suffix Tree](data_structures/suffix_tree/suffix_tree.py)
* [Suffix Tree Node](data_structures/suffix_tree/suffix_tree_node.py)
* Tests
* [Test Suffix Tree](data_structures/suffix_tree/tests/test_suffix_tree.py)
* Trie
* [Radix Tree](data_structures/trie/radix_tree.py)
* [Trie](data_structures/trie/trie.py)
@ -330,6 +351,9 @@
* [Power](divide_and_conquer/power.py)
* [Strassen Matrix Multiplication](divide_and_conquer/strassen_matrix_multiplication.py)
## Docs
* [Conf](docs/conf.py)
## Dynamic Programming
* [Abbreviation](dynamic_programming/abbreviation.py)
* [All Construct](dynamic_programming/all_construct.py)
@ -351,7 +375,7 @@
* [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py)
* [Longest Common Substring](dynamic_programming/longest_common_substring.py)
* [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py)
* [Longest Increasing Subsequence O(Nlogn)](dynamic_programming/longest_increasing_subsequence_o(nlogn).py)
* [Longest Increasing Subsequence O Nlogn](dynamic_programming/longest_increasing_subsequence_o_nlogn.py)
* [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py)
* [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py)
* [Matrix Chain Order](dynamic_programming/matrix_chain_order.py)
@ -465,7 +489,7 @@
* [Dijkstra Alternate](graphs/dijkstra_alternate.py)
* [Dijkstra Binary Grid](graphs/dijkstra_binary_grid.py)
* [Dinic](graphs/dinic.py)
* [Directed And Undirected (Weighted) Graph](graphs/directed_and_undirected_(weighted)_graph.py)
* [Directed And Undirected Weighted Graph](graphs/directed_and_undirected_weighted_graph.py)
* [Edmonds Karp Multiple Source And Sink](graphs/edmonds_karp_multiple_source_and_sink.py)
* [Eulerian Path And Circuit For Undirected Graph](graphs/eulerian_path_and_circuit_for_undirected_graph.py)
* [Even Tree](graphs/even_tree.py)
@ -540,8 +564,7 @@
* [Lu Decomposition](linear_algebra/lu_decomposition.py)
* Src
* [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py)
* Gaussian Elimination Pivoting
* [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py)
* [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting.py)
* [Lib](linear_algebra/src/lib.py)
* [Polynom For Points](linear_algebra/src/polynom_for_points.py)
* [Power Iteration](linear_algebra/src/power_iteration.py)
@ -661,7 +684,6 @@
* [Manhattan Distance](maths/manhattan_distance.py)
* [Matrix Exponentiation](maths/matrix_exponentiation.py)
* [Max Sum Sliding Window](maths/max_sum_sliding_window.py)
* [Median Of Two Arrays](maths/median_of_two_arrays.py)
* [Minkowski Distance](maths/minkowski_distance.py)
* [Mobius Function](maths/mobius_function.py)
* [Modular Division](maths/modular_division.py)
@ -773,6 +795,7 @@
* [Inverse Of Matrix](matrix/inverse_of_matrix.py)
* [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py)
* [Matrix Class](matrix/matrix_class.py)
* [Matrix Equalization](matrix/matrix_equalization.py)
* [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py)
* [Matrix Operation](matrix/matrix_operation.py)
* [Max Area Of Island](matrix/max_area_of_island.py)
@ -792,7 +815,6 @@
* [Minimum Cut](networking_flow/minimum_cut.py)
## Neural Network
* [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py)
* Activation Functions
* [Binary Step](neural_network/activation_functions/binary_step.py)
* [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py)
@ -809,6 +831,7 @@
* [Convolution Neural Network](neural_network/convolution_neural_network.py)
* [Input Data](neural_network/input_data.py)
* [Simple Neural Network](neural_network/simple_neural_network.py)
* [Two Hidden Layers Neural Network](neural_network/two_hidden_layers_neural_network.py)
## Other
* [Activity Selection](other/activity_selection.py)
@ -863,6 +886,7 @@
* [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py)
* [Photoelectric Effect](physics/photoelectric_effect.py)
* [Potential Energy](physics/potential_energy.py)
* [Rainfall Intensity](physics/rainfall_intensity.py)
* [Reynolds Number](physics/reynolds_number.py)
* [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py)
* [Shear Stress](physics/shear_stress.py)
@ -1184,6 +1208,7 @@
* [Binary Tree Traversal](searches/binary_tree_traversal.py)
* [Double Linear Search](searches/double_linear_search.py)
* [Double Linear Search Recursion](searches/double_linear_search_recursion.py)
* [Exponential Search](searches/exponential_search.py)
* [Fibonacci Search](searches/fibonacci_search.py)
* [Hill Climbing](searches/hill_climbing.py)
* [Interpolation Search](searches/interpolation_search.py)
@ -1259,6 +1284,7 @@
* [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py)
* [Capitalize](strings/capitalize.py)
* [Check Anagrams](strings/check_anagrams.py)
* [Count Vowels](strings/count_vowels.py)
* [Credit Card Validator](strings/credit_card_validator.py)
* [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py)
* [Detecting English Programmatically](strings/detecting_english_programmatically.py)
@ -1326,7 +1352,6 @@
* [Get Ip Geolocation](web_programming/get_ip_geolocation.py)
* [Get Top Billionaires](web_programming/get_top_billionaires.py)
* [Get Top Hn Posts](web_programming/get_top_hn_posts.py)
* [Get User Tweets](web_programming/get_user_tweets.py)
* [Giphy](web_programming/giphy.py)
* [Instagram Crawler](web_programming/instagram_crawler.py)
* [Instagram Pic](web_programming/instagram_pic.py)

View File

@ -1,4 +1,4 @@
MIT License
## MIT License
Copyright (c) 2016-2022 TheAlgorithms and contributors

View File

@ -13,7 +13,7 @@ Alternatively you can use scipy.signal.butter, which should yield the same resul
def make_lowpass(
frequency: int,
samplerate: int,
q_factor: float = 1 / sqrt(2), # noqa: B008
q_factor: float = 1 / sqrt(2),
) -> IIRFilter:
"""
Creates a low-pass filter
@ -43,7 +43,7 @@ def make_lowpass(
def make_highpass(
frequency: int,
samplerate: int,
q_factor: float = 1 / sqrt(2), # noqa: B008
q_factor: float = 1 / sqrt(2),
) -> IIRFilter:
"""
Creates a high-pass filter
@ -73,7 +73,7 @@ def make_highpass(
def make_bandpass(
frequency: int,
samplerate: int,
q_factor: float = 1 / sqrt(2), # noqa: B008
q_factor: float = 1 / sqrt(2),
) -> IIRFilter:
"""
Creates a band-pass filter
@ -104,7 +104,7 @@ def make_bandpass(
def make_allpass(
frequency: int,
samplerate: int,
q_factor: float = 1 / sqrt(2), # noqa: B008
q_factor: float = 1 / sqrt(2),
) -> IIRFilter:
"""
Creates an all-pass filter
@ -132,7 +132,7 @@ def make_peak(
frequency: int,
samplerate: int,
gain_db: float,
q_factor: float = 1 / sqrt(2), # noqa: B008
q_factor: float = 1 / sqrt(2),
) -> IIRFilter:
"""
Creates a peak filter
@ -164,7 +164,7 @@ def make_lowshelf(
frequency: int,
samplerate: int,
gain_db: float,
q_factor: float = 1 / sqrt(2), # noqa: B008
q_factor: float = 1 / sqrt(2),
) -> IIRFilter:
"""
Creates a low-shelf filter
@ -201,7 +201,7 @@ def make_highshelf(
frequency: int,
samplerate: int,
gain_db: float,
q_factor: float = 1 / sqrt(2), # noqa: B008
q_factor: float = 1 / sqrt(2),
) -> IIRFilter:
"""
Creates a high-shelf filter

View File

@ -1,5 +1,6 @@
from __future__ import annotations
from abc import abstractmethod
from math import pi
from typing import Protocol
@ -8,6 +9,7 @@ import numpy as np
class FilterType(Protocol):
@abstractmethod
def process(self, sample: float) -> float:
"""
Calculate y[n]
@ -15,7 +17,6 @@ class FilterType(Protocol):
>>> issubclass(FilterType, Protocol)
True
"""
return 0.0
def get_bounds(

View File

@ -23,6 +23,42 @@ def create_state_space_tree(
Creates a state space tree to iterate through each branch using DFS.
We know that each state has exactly len(sequence) - index children.
It terminates when it reaches the end of the given sequence.
:param sequence: The input sequence for which permutations are generated.
:param current_sequence: The current permutation being built.
:param index: The current index in the sequence.
:param index_used: list to track which elements are used in permutation.
Example 1:
>>> sequence = [1, 2, 3]
>>> current_sequence = []
>>> index_used = [False, False, False]
>>> create_state_space_tree(sequence, current_sequence, 0, index_used)
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
Example 2:
>>> sequence = ["A", "B", "C"]
>>> current_sequence = []
>>> index_used = [False, False, False]
>>> create_state_space_tree(sequence, current_sequence, 0, index_used)
['A', 'B', 'C']
['A', 'C', 'B']
['B', 'A', 'C']
['B', 'C', 'A']
['C', 'A', 'B']
['C', 'B', 'A']
Example 3:
>>> sequence = [1]
>>> current_sequence = []
>>> index_used = [False]
>>> create_state_space_tree(sequence, current_sequence, 0, index_used)
[1]
"""
if index == len(sequence):

View File

@ -22,6 +22,56 @@ def create_state_space_tree(
Creates a state space tree to iterate through each branch using DFS.
We know that each state has exactly two children.
It terminates when it reaches the end of the given sequence.
:param sequence: The input sequence for which subsequences are generated.
:param current_subsequence: The current subsequence being built.
:param index: The current index in the sequence.
Example:
>>> sequence = [3, 2, 1]
>>> current_subsequence = []
>>> create_state_space_tree(sequence, current_subsequence, 0)
[]
[1]
[2]
[2, 1]
[3]
[3, 1]
[3, 2]
[3, 2, 1]
>>> sequence = ["A", "B"]
>>> current_subsequence = []
>>> create_state_space_tree(sequence, current_subsequence, 0)
[]
['B']
['A']
['A', 'B']
>>> sequence = []
>>> current_subsequence = []
>>> create_state_space_tree(sequence, current_subsequence, 0)
[]
>>> sequence = [1, 2, 3, 4]
>>> current_subsequence = []
>>> create_state_space_tree(sequence, current_subsequence, 0)
[]
[4]
[3]
[3, 4]
[2]
[2, 4]
[2, 3]
[2, 3, 4]
[1]
[1, 4]
[1, 3]
[1, 3, 4]
[1, 2]
[1, 2, 4]
[1, 2, 3]
[1, 2, 3, 4]
"""
if index == len(sequence):
@ -35,7 +85,7 @@ def create_state_space_tree(
if __name__ == "__main__":
seq: list[Any] = [3, 1, 2, 4]
seq: list[Any] = [1, 2, 3]
generate_all_subsequences(seq)
seq.clear()

View File

@ -28,9 +28,8 @@ def is_valid(
if vertical:
if row + i >= len(puzzle) or puzzle[row + i][col] != "":
return False
else:
if col + i >= len(puzzle[0]) or puzzle[row][col + i] != "":
return False
elif col + i >= len(puzzle[0]) or puzzle[row][col + i] != "":
return False
return True

View File

@ -24,10 +24,10 @@ def get_valid_pos(position: tuple[int, int], n: int) -> list[tuple[int, int]]:
]
permissible_positions = []
for position in positions:
y_test, x_test = position
for inner_position in positions:
y_test, x_test = inner_position
if 0 <= y_test < n and 0 <= x_test < n:
permissible_positions.append(position)
permissible_positions.append(inner_position)
return permissible_positions

View File

@ -1,7 +1,7 @@
"""
Given a partially filled 9×9 2D array, the objective is to fill a 9×9
Given a partially filled 9x9 2D array, the objective is to fill a 9x9
square grid with digits numbered 1 to 9, so that every row, column, and
and each of the nine 3×3 sub-grids contains all of the digits.
and each of the nine 3x3 sub-grids contains all of the digits.
This can be solved using Backtracking and is similar to n-queens.
We check to see if a cell is safe or not and recursively call the

View File

@ -0,0 +1,71 @@
"""
Word Break Problem is a well-known problem in computer science.
Given a string and a dictionary of words, the task is to determine if
the string can be segmented into a sequence of one or more dictionary words.
Wikipedia: https://en.wikipedia.org/wiki/Word_break_problem
"""
def backtrack(input_string: str, word_dict: set[str], start: int) -> bool:
"""
Helper function that uses backtracking to determine if a valid
word segmentation is possible starting from index 'start'.
Parameters:
input_string (str): The input string to be segmented.
word_dict (set[str]): A set of valid dictionary words.
start (int): The starting index of the substring to be checked.
Returns:
bool: True if a valid segmentation is possible, otherwise False.
Example:
>>> backtrack("leetcode", {"leet", "code"}, 0)
True
>>> backtrack("applepenapple", {"apple", "pen"}, 0)
True
>>> backtrack("catsandog", {"cats", "dog", "sand", "and", "cat"}, 0)
False
"""
# Base case: if the starting index has reached the end of the string
if start == len(input_string):
return True
# Try every possible substring from 'start' to 'end'
for end in range(start + 1, len(input_string) + 1):
if input_string[start:end] in word_dict and backtrack(
input_string, word_dict, end
):
return True
return False
def word_break(input_string: str, word_dict: set[str]) -> bool:
"""
Determines if the input string can be segmented into a sequence of
valid dictionary words using backtracking.
Parameters:
input_string (str): The input string to segment.
word_dict (set[str]): The set of valid words.
Returns:
bool: True if the string can be segmented into valid words, otherwise False.
Example:
>>> word_break("leetcode", {"leet", "code"})
True
>>> word_break("applepenapple", {"apple", "pen"})
True
>>> word_break("catsandog", {"cats", "dog", "sand", "and", "cat"})
False
"""
return backtrack(input_string, word_dict, 0)

100
backtracking/word_ladder.py Normal file
View File

@ -0,0 +1,100 @@
"""
Word Ladder is a classic problem in computer science.
The problem is to transform a start word into an end word
by changing one letter at a time.
Each intermediate word must be a valid word from a given list of words.
The goal is to find a transformation sequence
from the start word to the end word.
Wikipedia: https://en.wikipedia.org/wiki/Word_ladder
"""
import string
def backtrack(
current_word: str, path: list[str], end_word: str, word_set: set[str]
) -> list[str]:
"""
Helper function to perform backtracking to find the transformation
from the current_word to the end_word.
Parameters:
current_word (str): The current word in the transformation sequence.
path (list[str]): The list of transformations from begin_word to current_word.
end_word (str): The target word for transformation.
word_set (set[str]): The set of valid words for transformation.
Returns:
list[str]: The list of transformations from begin_word to end_word.
Returns an empty list if there is no valid
transformation from current_word to end_word.
Example:
>>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log", "cog"})
['hit', 'hot', 'dot', 'lot', 'log', 'cog']
>>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log"})
[]
>>> backtrack("lead", ["lead"], "gold", {"load", "goad", "gold", "lead", "lord"})
['lead', 'lead', 'load', 'goad', 'gold']
>>> backtrack("game", ["game"], "code", {"came", "cage", "code", "cade", "gave"})
['game', 'came', 'cade', 'code']
"""
# Base case: If the current word is the end word, return the path
if current_word == end_word:
return path
# Try all possible single-letter transformations
for i in range(len(current_word)):
for c in string.ascii_lowercase: # Try changing each letter
transformed_word = current_word[:i] + c + current_word[i + 1 :]
if transformed_word in word_set:
word_set.remove(transformed_word)
# Recur with the new word added to the path
result = backtrack(
transformed_word, [*path, transformed_word], end_word, word_set
)
if result: # valid transformation found
return result
word_set.add(transformed_word) # backtrack
return [] # No valid transformation found
def word_ladder(begin_word: str, end_word: str, word_set: set[str]) -> list[str]:
"""
Solve the Word Ladder problem using Backtracking and return
the list of transformations from begin_word to end_word.
Parameters:
begin_word (str): The word from which the transformation starts.
end_word (str): The target word for transformation.
word_list (list[str]): The list of valid words for transformation.
Returns:
list[str]: The list of transformations from begin_word to end_word.
Returns an empty list if there is no valid transformation.
Example:
>>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"])
['hit', 'hot', 'dot', 'lot', 'log', 'cog']
>>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log"])
[]
>>> word_ladder("lead", "gold", ["load", "goad", "gold", "lead", "lord"])
['lead', 'lead', 'load', 'goad', 'gold']
>>> word_ladder("game", "code", ["came", "cage", "code", "cade", "gave"])
['game', 'came', 'cade', 'code']
"""
if end_word not in word_set: # no valid transformation possible
return []
# Perform backtracking starting from the begin_word
return backtrack(begin_word, [begin_word], end_word, word_set)

View File

@ -26,7 +26,7 @@ def binary_and(a: int, b: int) -> str:
>>> binary_and(0, 1.1)
Traceback (most recent call last):
...
TypeError: 'float' object cannot be interpreted as an integer
ValueError: Unknown format code 'b' for object of type 'float'
>>> binary_and("0", "1")
Traceback (most recent call last):
...
@ -35,8 +35,8 @@ def binary_and(a: int, b: int) -> str:
if a < 0 or b < 0:
raise ValueError("the value of both inputs must be positive")
a_binary = str(bin(a))[2:] # remove the leading "0b"
b_binary = str(bin(b))[2:] # remove the leading "0b"
a_binary = format(a, "b")
b_binary = format(b, "b")
max_len = max(len(a_binary), len(b_binary))

View File

@ -8,8 +8,8 @@ def set_bit(number: int, position: int) -> int:
Set the bit at position to 1.
Details: perform bitwise or for given number and X.
Where X is a number with all the bits zeroes and bit on given
position one.
Where X is a number with all the bits - zeroes and bit on given
position - one.
>>> set_bit(0b1101, 1) # 0b1111
15
@ -26,8 +26,8 @@ def clear_bit(number: int, position: int) -> int:
Set the bit at position to 0.
Details: perform bitwise and for given number and X.
Where X is a number with all the bits ones and bit on given
position zero.
Where X is a number with all the bits - ones and bit on given
position - zero.
>>> clear_bit(0b10010, 1) # 0b10000
16
@ -42,8 +42,8 @@ def flip_bit(number: int, position: int) -> int:
Flip the bit at position.
Details: perform bitwise xor for given number and X.
Where X is a number with all the bits zeroes and bit on given
position one.
Where X is a number with all the bits - zeroes and bit on given
position - one.
>>> flip_bit(0b101, 1) # 0b111
7
@ -79,7 +79,7 @@ def get_bit(number: int, position: int) -> int:
Get the bit at the given position
Details: perform bitwise and for the given number and X,
Where X is a number with all the bits zeroes and bit on given position one.
Where X is a number with all the bits - zeroes and bit on given position - one.
If the result is not equal to 0, then the bit on the given position is 1, else 0.
>>> get_bit(0b1010, 0)

View File

@ -101,9 +101,8 @@ def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool:
state = True
elif alive > 3:
state = False
else:
if alive == 3:
state = True
elif alive == 3:
state = True
return state

View File

@ -24,6 +24,14 @@ def encrypt(plaintext: str, key: str) -> str:
Traceback (most recent call last):
...
ValueError: plaintext is empty
>>> encrypt("coffee is good as python", "")
Traceback (most recent call last):
...
ValueError: key is empty
>>> encrypt(527.26, "TheAlgorithms")
Traceback (most recent call last):
...
TypeError: plaintext must be a string
"""
if not isinstance(plaintext, str):
raise TypeError("plaintext must be a string")
@ -80,6 +88,14 @@ def decrypt(ciphertext: str, key: str) -> str:
Traceback (most recent call last):
...
TypeError: ciphertext must be a string
>>> decrypt("", "TheAlgorithms")
Traceback (most recent call last):
...
ValueError: ciphertext is empty
>>> decrypt("vvjfpk wj ohvp su ddylsv", 2)
Traceback (most recent call last):
...
TypeError: key must be a string
"""
if not isinstance(ciphertext, str):
raise TypeError("ciphertext must be a string")

View File

@ -206,20 +206,19 @@ def decrypt_caesar_with_chi_squared(
# Add the margin of error to the total chi squared statistic
chi_squared_statistic += chi_letter_value
else:
if letter.lower() in frequencies:
# Get the amount of times the letter occurs in the message
occurrences = decrypted_with_shift.count(letter)
elif letter.lower() in frequencies:
# Get the amount of times the letter occurs in the message
occurrences = decrypted_with_shift.count(letter)
# Get the excepcted amount of times the letter should appear based
# on letter frequencies
expected = frequencies[letter] * occurrences
# Get the excepcted amount of times the letter should appear based
# on letter frequencies
expected = frequencies[letter] * occurrences
# Complete the chi squared statistic formula
chi_letter_value = ((occurrences - expected) ** 2) / expected
# Complete the chi squared statistic formula
chi_letter_value = ((occurrences - expected) ** 2) / expected
# Add the margin of error to the total chi squared statistic
chi_squared_statistic += chi_letter_value
# Add the margin of error to the total chi squared statistic
chi_squared_statistic += chi_letter_value
# Add the data to the chi_squared_statistic_values dictionary
chi_squared_statistic_values[shift] = (

View File

@ -0,0 +1,45 @@
from string import ascii_uppercase
def gronsfeld(text: str, key: str) -> str:
"""
Encrypt plaintext with the Gronsfeld cipher
>>> gronsfeld('hello', '412')
'LFNPP'
>>> gronsfeld('hello', '123')
'IGOMQ'
>>> gronsfeld('', '123')
''
>>> gronsfeld('yes, ¥€$ - _!@#%?', '0')
'YES, ¥€$ - _!@#%?'
>>> gronsfeld('yes, ¥€$ - _!@#%?', '01')
'YFS, ¥€$ - _!@#%?'
>>> gronsfeld('yes, ¥€$ - _!@#%?', '012')
'YFU, ¥€$ - _!@#%?'
>>> gronsfeld('yes, ¥€$ - _!@#%?', '')
Traceback (most recent call last):
...
ZeroDivisionError: integer modulo by zero
"""
ascii_len = len(ascii_uppercase)
key_len = len(key)
encrypted_text = ""
keys = [int(char) for char in key]
upper_case_text = text.upper()
for i, char in enumerate(upper_case_text):
if char in ascii_uppercase:
new_position = (ascii_uppercase.index(char) + keys[i % key_len]) % ascii_len
shifted_letter = ascii_uppercase[new_position]
encrypted_text += shifted_letter
else:
encrypted_text += char
return encrypted_text
if __name__ == "__main__":
from doctest import testmod
testmod()

View File

@ -38,7 +38,7 @@ https://www.youtube.com/watch?v=4RhLNDqcjpA
import string
import numpy
import numpy as np
from maths.greatest_common_divisor import greatest_common_divisor
@ -49,11 +49,11 @@ class HillCipher:
# i.e. a total of 36 characters
# take x and return x % len(key_string)
modulus = numpy.vectorize(lambda x: x % 36)
modulus = np.vectorize(lambda x: x % 36)
to_int = numpy.vectorize(round)
to_int = np.vectorize(round)
def __init__(self, encrypt_key: numpy.ndarray) -> None:
def __init__(self, encrypt_key: np.ndarray) -> None:
"""
encrypt_key is an NxN numpy array
"""
@ -63,7 +63,7 @@ class HillCipher:
def replace_letters(self, letter: str) -> int:
"""
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
>>> hill_cipher.replace_letters('T')
19
>>> hill_cipher.replace_letters('0')
@ -73,7 +73,7 @@ class HillCipher:
def replace_digits(self, num: int) -> str:
"""
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
>>> hill_cipher.replace_digits(19)
'T'
>>> hill_cipher.replace_digits(26)
@ -83,10 +83,10 @@ class HillCipher:
def check_determinant(self) -> None:
"""
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
>>> hill_cipher.check_determinant()
"""
det = round(numpy.linalg.det(self.encrypt_key))
det = round(np.linalg.det(self.encrypt_key))
if det < 0:
det = det % len(self.key_string)
@ -101,7 +101,7 @@ class HillCipher:
def process_text(self, text: str) -> str:
"""
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
>>> hill_cipher.process_text('Testing Hill Cipher')
'TESTINGHILLCIPHERR'
>>> hill_cipher.process_text('hello')
@ -117,7 +117,7 @@ class HillCipher:
def encrypt(self, text: str) -> str:
"""
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
>>> hill_cipher.encrypt('testing hill cipher')
'WHXYJOLM9C6XT085LL'
>>> hill_cipher.encrypt('hello')
@ -129,7 +129,7 @@ class HillCipher:
for i in range(0, len(text) - self.break_key + 1, self.break_key):
batch = text[i : i + self.break_key]
vec = [self.replace_letters(char) for char in batch]
batch_vec = numpy.array([vec]).T
batch_vec = np.array([vec]).T
batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[
0
]
@ -140,14 +140,14 @@ class HillCipher:
return encrypted
def make_decrypt_key(self) -> numpy.ndarray:
def make_decrypt_key(self) -> np.ndarray:
"""
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
>>> hill_cipher.make_decrypt_key()
array([[ 6, 25],
[ 5, 26]])
"""
det = round(numpy.linalg.det(self.encrypt_key))
det = round(np.linalg.det(self.encrypt_key))
if det < 0:
det = det % len(self.key_string)
@ -158,16 +158,14 @@ class HillCipher:
break
inv_key = (
det_inv
* numpy.linalg.det(self.encrypt_key)
* numpy.linalg.inv(self.encrypt_key)
det_inv * np.linalg.det(self.encrypt_key) * np.linalg.inv(self.encrypt_key)
)
return self.to_int(self.modulus(inv_key))
def decrypt(self, text: str) -> str:
"""
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
>>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL')
'TESTINGHILLCIPHERR'
>>> hill_cipher.decrypt('85FF00')
@ -180,7 +178,7 @@ class HillCipher:
for i in range(0, len(text) - self.break_key + 1, self.break_key):
batch = text[i : i + self.break_key]
vec = [self.replace_letters(char) for char in batch]
batch_vec = numpy.array([vec]).T
batch_vec = np.array([vec]).T
batch_decrypted = self.modulus(decrypt_key.dot(batch_vec)).T.tolist()[0]
decrypted_batch = "".join(
self.replace_digits(num) for num in batch_decrypted
@ -199,7 +197,7 @@ def main() -> None:
row = [int(x) for x in input().split()]
hill_matrix.append(row)
hc = HillCipher(numpy.array(hill_matrix))
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")

View File

@ -1,7 +1,7 @@
"""
https://en.wikipedia.org/wiki/Burrows%E2%80%93Wheeler_transform
The BurrowsWheeler transform (BWT, also called block-sorting compression)
The Burrows-Wheeler transform (BWT, also called block-sorting compression)
rearranges a character string into runs of similar characters. This is useful
for compression, since it tends to be easy to compress a string that has runs
of repeated characters by techniques such as move-to-front transform and

View File

@ -40,7 +40,7 @@ def build_tree(letters: list[Letter]) -> Letter | TreeNode:
Run through the list of Letters and build the min heap
for the Huffman Tree.
"""
response: list[Letter | TreeNode] = letters # type: ignore
response: list[Letter | TreeNode] = list(letters)
while len(response) > 1:
left = response.pop(0)
right = response.pop(0)
@ -59,7 +59,7 @@ def traverse_tree(root: Letter | TreeNode, bitstring: str) -> list[Letter]:
if isinstance(root, Letter):
root.bitstring[root.letter] = bitstring
return [root]
treenode: TreeNode = root # type: ignore
treenode: TreeNode = root
letters = []
letters += traverse_tree(treenode.left, bitstring + "0")
letters += traverse_tree(treenode.right, bitstring + "1")

View File

@ -1,5 +1,5 @@
"""
One of the several implementations of LempelZivWelch compression algorithm
One of the several implementations of Lempel-Ziv-Welch compression algorithm
https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch
"""
@ -43,7 +43,7 @@ def add_key_to_lexicon(
def compress_data(data_bits: str) -> str:
"""
Compresses given data_bits using LempelZivWelch compression algorithm
Compresses given data_bits using Lempel-Ziv-Welch compression algorithm
and returns the result as a string
"""
lexicon = {"0": "0", "1": "1"}

View File

@ -1,5 +1,5 @@
"""
One of the several implementations of LempelZivWelch decompression algorithm
One of the several implementations of Lempel-Ziv-Welch decompression algorithm
https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch
"""
@ -26,7 +26,7 @@ def read_file_binary(file_path: str) -> str:
def decompress_data(data_bits: str) -> str:
"""
Decompresses given data_bits using LempelZivWelch compression algorithm
Decompresses given data_bits using Lempel-Ziv-Welch compression algorithm
and returns the result as a string
"""
lexicon = {"0": "0", "1": "1"}

View File

@ -25,7 +25,7 @@ import numpy as np
# Importing the Keras libraries and packages
import tensorflow as tf
from tensorflow.keras import layers, models
from keras import layers, models
if __name__ == "__main__":
# Initialising the CNN

View File

@ -19,7 +19,7 @@ def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float
>>> root_mean_square_error(np.array([1, 2, 3]), np.array([6, 4, 2]))
3.1622776601683795
"""
return np.sqrt(((original - reference) ** 2).mean())
return float(np.sqrt(((original - reference) ** 2).mean()))
def normalize_image(
@ -141,7 +141,7 @@ def transform(
center_x, center_y = (x // 2 for x in kernel.shape)
# Use padded image when applying convolotion
# Use padded image when applying convolution
# to not go out of bounds of the original the image
transformed = np.zeros(image.shape, dtype=np.uint8)
padded = np.pad(image, 1, "constant", constant_values=constant)
@ -273,7 +273,7 @@ def haralick_descriptors(matrix: np.ndarray) -> list[float]:
>>> morphological = opening_filter(binary)
>>> mask_1 = binary_mask(gray, morphological)[0]
>>> concurrency = matrix_concurrency(mask_1, (0, 1))
>>> haralick_descriptors(concurrency)
>>> [float(f) for f in haralick_descriptors(concurrency)]
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
"""
# Function np.indices could be used for bigger input types,
@ -335,7 +335,7 @@ def get_descriptors(
return np.concatenate(descriptors, axis=None)
def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32:
def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> float:
"""
Simple method for calculating the euclidean distance between two points,
with type np.ndarray.
@ -346,7 +346,7 @@ def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32:
>>> euclidean(a, b)
3.3166247903554
"""
return np.sqrt(np.sum(np.square(point_1 - point_2)))
return float(np.sqrt(np.sum(np.square(point_1 - point_2))))
def get_distances(descriptors: np.ndarray, base: int) -> list[tuple[int, float]]:

View File

@ -297,6 +297,12 @@ def weight_conversion(from_type: str, to_type: str, value: float) -> float:
1.660540199e-23
>>> weight_conversion("atomic-mass-unit","atomic-mass-unit",2)
1.999999998903455
>>> weight_conversion("slug", "kilogram", 1)
Traceback (most recent call last):
...
ValueError: Invalid 'from_type' or 'to_type' value: 'slug', 'kilogram'
Supported values are: kilogram, gram, milligram, metric-ton, long-ton, short-ton, \
pound, stone, ounce, carrat, atomic-mass-unit
"""
if to_type not in KILOGRAM_CHART or from_type not in WEIGHT_TYPE_CHART:
msg = (

View File

View File

@ -92,10 +92,9 @@ def eliminate(values, s, d):
dplaces = [s for s in u if d in values[s]]
if len(dplaces) == 0:
return False ## Contradiction: no place for this value
elif len(dplaces) == 1:
# d can only be in one place in unit; assign it there
if not assign(values, dplaces[0], d):
return False
# d can only be in one place in unit; assign it there
elif len(dplaces) == 1 and not assign(values, dplaces[0], d):
return False
return values
@ -151,7 +150,7 @@ def solve_all(grids, name="", showif=0.0):
display(grid_values(grid))
if values:
display(values)
print("(%.5f seconds)\n" % t)
print(f"({t:.5f} seconds)\n")
return (t, solved(values))
times, results = zip(*[time_solve(grid) for grid in grids])
@ -173,7 +172,7 @@ def solved(values):
def from_file(filename, sep="\n"):
"Parse a file into a list of strings, separated by sep."
return open(filename).read().strip().split(sep) # noqa: SIM115
return open(filename).read().strip().split(sep)
def random_puzzle(assignments=17):
@ -218,4 +217,4 @@ if __name__ == "__main__":
start = time.monotonic()
solve(puzzle)
t = time.monotonic() - start
print("Solved: %.5f sec" % t)
print(f"Solved: {t:.5f} sec")

View File

@ -215,11 +215,11 @@ def del_node(root: MyNode, data: Any) -> MyNode | None:
return root
else:
root.set_left(del_node(left_child, data))
else: # root.get_data() < data
if right_child is None:
return root
else:
root.set_right(del_node(right_child, data))
# root.get_data() < data
elif right_child is None:
return root
else:
root.set_right(del_node(right_child, data))
if get_height(right_child) - get_height(left_child) == 2:
assert right_child is not None

View File

@ -85,7 +85,7 @@ class BinaryTree:
"""
return self._depth(self.root)
def _depth(self, node: Node | None) -> int: # noqa: UP007
def _depth(self, node: Node | None) -> int:
if not node:
return 0
return 1 + max(self._depth(node.left), self._depth(node.right))

View File

@ -185,12 +185,11 @@ class BinarySearchTree:
break
else:
parent_node = parent_node.left
elif parent_node.right is None:
parent_node.right = new_node
break
else:
if parent_node.right is None:
parent_node.right = new_node
break
else:
parent_node = parent_node.right
parent_node = parent_node.right
new_node.parent = parent_node
def insert(self, *values) -> Self:
@ -295,9 +294,9 @@ class BinarySearchTree:
predecessor = self.get_max(
node.left
) # Gets the max value of the left branch
self.remove(predecessor.value) # type: ignore
self.remove(predecessor.value) # type: ignore[union-attr]
node.value = (
predecessor.value # type: ignore
predecessor.value # type: ignore[union-attr]
) # Assigns the value to the node to delete and keep tree structure
def preorder_traverse(self, node: Node | None) -> Iterable:
@ -337,7 +336,7 @@ def inorder(curr_node: Node | None) -> list[Node]:
"""
node_list = []
if curr_node is not None:
node_list = inorder(curr_node.left) + [curr_node] + inorder(curr_node.right)
node_list = [*inorder(curr_node.left), curr_node, *inorder(curr_node.right)]
return node_list

View File

@ -74,14 +74,13 @@ class BinarySearchTree:
def _put(self, node: Node | None, label: int, parent: Node | None = None) -> Node:
if node is None:
node = Node(label, parent)
elif label < node.label:
node.left = self._put(node.left, label, node)
elif label > node.label:
node.right = self._put(node.right, label, node)
else:
if label < node.label:
node.left = self._put(node.left, label, node)
elif label > node.label:
node.right = self._put(node.right, label, node)
else:
msg = f"Node with label {label} already exists"
raise ValueError(msg)
msg = f"Node with label {label} already exists"
raise ValueError(msg)
return node
@ -106,11 +105,10 @@ class BinarySearchTree:
if node is None:
msg = f"Node with label {label} does not exist"
raise ValueError(msg)
else:
if label < node.label:
node = self._search(node.left, label)
elif label > node.label:
node = self._search(node.right, label)
elif label < node.label:
node = self._search(node.left, label)
elif label > node.label:
node = self._search(node.right, label)
return node

View File

@ -97,6 +97,8 @@ def level_order(root: Node | None) -> Generator[int, None, None]:
"""
Returns a list of nodes value from a whole binary tree in Level Order Traverse.
Level Order traverse: Visit nodes of the tree level-by-level.
>>> list(level_order(make_tree()))
[1, 2, 3, 4, 5]
"""
if root is None:
@ -120,6 +122,10 @@ def get_nodes_from_left_to_right(
"""
Returns a list of nodes value from a particular level:
Left to right direction of the binary tree.
>>> list(get_nodes_from_left_to_right(make_tree(), 1))
[1]
>>> list(get_nodes_from_left_to_right(make_tree(), 2))
[2, 3]
"""
def populate_output(root: Node | None, level: int) -> Generator[int, None, None]:
@ -140,10 +146,14 @@ def get_nodes_from_right_to_left(
"""
Returns a list of nodes value from a particular level:
Right to left direction of the binary tree.
>>> list(get_nodes_from_right_to_left(make_tree(), 1))
[1]
>>> list(get_nodes_from_right_to_left(make_tree(), 2))
[3, 2]
"""
def populate_output(root: Node | None, level: int) -> Generator[int, None, None]:
if root is None:
if not root:
return
if level == 1:
yield root.data
@ -158,6 +168,8 @@ def zigzag(root: Node | None) -> Generator[int, None, None]:
"""
ZigZag traverse:
Returns a list of nodes value from left to right and right to left, alternatively.
>>> list(zigzag(make_tree()))
[1, 3, 2, 4, 5]
"""
if root is None:
return

View File

@ -80,9 +80,9 @@ class Node:
"""
if self.left and (self.data < self.left.data or not self.left.is_sorted):
return False
if self.right and (self.data > self.right.data or not self.right.is_sorted):
return False
return True
return not (
self.right and (self.data > self.right.data or not self.right.is_sorted)
)
if __name__ == "__main__":

View File

@ -0,0 +1,78 @@
from __future__ import annotations
import sys
from dataclasses import dataclass
INT_MIN = -sys.maxsize + 1
INT_MAX = sys.maxsize - 1
@dataclass
class TreeNode:
val: int = 0
left: TreeNode | None = None
right: TreeNode | None = None
def max_sum_bst(root: TreeNode | None) -> int:
"""
The solution traverses a binary tree to find the maximum sum of
keys in any subtree that is a Binary Search Tree (BST). It uses
recursion to validate BST properties and calculates sums, returning
the highest sum found among all valid BST subtrees.
>>> t1 = TreeNode(4)
>>> t1.left = TreeNode(3)
>>> t1.left.left = TreeNode(1)
>>> t1.left.right = TreeNode(2)
>>> print(max_sum_bst(t1))
2
>>> t2 = TreeNode(-4)
>>> t2.left = TreeNode(-2)
>>> t2.right = TreeNode(-5)
>>> print(max_sum_bst(t2))
0
>>> t3 = TreeNode(1)
>>> t3.left = TreeNode(4)
>>> t3.left.left = TreeNode(2)
>>> t3.left.right = TreeNode(4)
>>> t3.right = TreeNode(3)
>>> t3.right.left = TreeNode(2)
>>> t3.right.right = TreeNode(5)
>>> t3.right.right.left = TreeNode(4)
>>> t3.right.right.right = TreeNode(6)
>>> print(max_sum_bst(t3))
20
"""
ans: int = 0
def solver(node: TreeNode | None) -> tuple[bool, int, int, int]:
"""
Returns the maximum sum by making recursive calls
>>> t1 = TreeNode(1)
>>> print(solver(t1))
1
"""
nonlocal ans
if not node:
return True, INT_MAX, INT_MIN, 0 # Valid BST, min, max, sum
is_left_valid, min_left, max_left, sum_left = solver(node.left)
is_right_valid, min_right, max_right, sum_right = solver(node.right)
if is_left_valid and is_right_valid and max_left < node.val < min_right:
total_sum = sum_left + sum_right + node.val
ans = max(ans, total_sum)
return True, min(min_left, node.val), max(max_right, node.val), total_sum
return False, -1, -1, -1 # Not a valid BST
solver(root)
return ans
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -87,12 +87,12 @@ class SegmentTree(Generic[T]):
p = p // 2
self.st[p] = self.fn(self.st[p * 2], self.st[p * 2 + 1])
def query(self, l: int, r: int) -> T | None: # noqa: E741
def query(self, left: int, right: int) -> T | None:
"""
Get range query value in log(N) time
:param l: left element index
:param r: right element index
:return: element combined in the range [l, r]
:param left: left element index
:param right: right element index
:return: element combined in the range [left, right]
>>> st = SegmentTree([1, 2, 3, 4], lambda a, b: a + b)
>>> st.query(0, 2)
@ -104,15 +104,15 @@ class SegmentTree(Generic[T]):
>>> st.query(2, 3)
7
"""
l, r = l + self.N, r + self.N
left, right = left + self.N, right + self.N
res: T | None = None
while l <= r:
if l % 2 == 1:
res = self.st[l] if res is None else self.fn(res, self.st[l])
if r % 2 == 0:
res = self.st[r] if res is None else self.fn(res, self.st[r])
l, r = (l + 1) // 2, (r - 1) // 2
while left <= right:
if left % 2 == 1:
res = self.st[left] if res is None else self.fn(res, self.st[left])
if right % 2 == 0:
res = self.st[right] if res is None else self.fn(res, self.st[right])
left, right = (left + 1) // 2, (right - 1) // 2
return res

View File

@ -31,8 +31,7 @@ def binomial_coefficient(n: int, k: int) -> int:
"""
result = 1 # To kept the Calculated Value
# Since C(n, k) = C(n, n-k)
if k > (n - k):
k = n - k
k = min(k, n - k)
# Calculate C(n,k)
for i in range(k):
result *= n - i

View File

@ -1,8 +1,3 @@
"""
psf/black : true
ruff : passed
"""
from __future__ import annotations
from collections.abc import Iterator
@ -17,7 +12,7 @@ class RedBlackTree:
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/Redblack_tree
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.
"""
@ -107,12 +102,11 @@ class RedBlackTree:
else:
self.left = RedBlackTree(label, 1, self)
self.left._insert_repair()
elif self.right:
self.right.insert(label)
else:
if self.right:
self.right.insert(label)
else:
self.right = RedBlackTree(label, 1, self)
self.right._insert_repair()
self.right = RedBlackTree(label, 1, self)
self.right._insert_repair()
return self.parent or self
def _insert_repair(self) -> None:
@ -153,7 +147,7 @@ class RedBlackTree:
self.grandparent.color = 1
self.grandparent._insert_repair()
def remove(self, label: int) -> RedBlackTree: # noqa: PLR0912
def remove(self, label: int) -> RedBlackTree:
"""Remove label from this tree."""
if self.label == label:
if self.left and self.right:
@ -178,36 +172,34 @@ class RedBlackTree:
self.parent.left = None
else:
self.parent.right = None
else:
# The node is black
if 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
# 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:
# 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
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)
else:
if self.right:
self.right.remove(label)
elif self.right:
self.right.remove(label)
return self.parent or self
def _remove_repair(self) -> None:
@ -324,9 +316,7 @@ class RedBlackTree:
return False
if self.left and not self.left.check_coloring():
return False
if self.right and not self.right.check_coloring():
return False
return True
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
@ -369,11 +359,10 @@ class RedBlackTree:
return None
else:
return self.right.search(label)
elif self.left is None:
return None
else:
if self.left is None:
return None
else:
return self.left.search(label)
return self.left.search(label)
def floor(self, label: int) -> int | None:
"""Returns the largest element in this tree which is at most label.
@ -565,9 +554,7 @@ def test_rotations() -> bool:
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)
if tree != right_rot:
return False
return True
return tree == right_rot
def test_insertion_speed() -> bool:
@ -610,13 +597,11 @@ def test_insert_and_search() -> bool:
tree.insert(12)
tree.insert(10)
tree.insert(11)
if 5 in tree or -6 in tree or -10 in tree or 13 in tree:
if any(i in tree for i in (5, -6, -10, 13)):
# Found something not in there
return False
if not (11 in tree and 12 in tree and -8 in tree and 0 in tree):
# Didn't find something in there
return False
return True
# Find all these things in there
return all(i in tree for i in (11, 12, -8, 0))
def test_insert_delete() -> bool:
@ -638,9 +623,7 @@ def test_insert_delete() -> bool:
tree = tree.remove(9)
if not tree.check_color_properties():
return False
if list(tree.inorder_traverse()) != [-8, 0, 4, 8, 10, 11, 12]:
return False
return True
return list(tree.inorder_traverse()) == [-8, 0, 4, 8, 10, 11, 12]
def test_floor_ceil() -> bool:
@ -668,9 +651,7 @@ def test_min_max() -> bool:
tree.insert(24)
tree.insert(20)
tree.insert(22)
if tree.get_max() != 22 or tree.get_min() != -16:
return False
return True
return not (tree.get_max() != 22 or tree.get_min() != -16)
def test_tree_traversal() -> bool:
@ -686,9 +667,7 @@ def test_tree_traversal() -> bool:
return False
if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]:
return False
if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]:
return False
return True
return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0]
def test_tree_chaining() -> bool:
@ -699,9 +678,7 @@ def test_tree_chaining() -> bool:
return False
if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]:
return False
if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]:
return False
return True
return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0]
def print_results(msg: str, passes: bool) -> None:

View File

@ -35,13 +35,13 @@ class SegmentTree:
"""
return idx * 2 + 1
def build(self, idx, l, r): # noqa: E741
if l == r:
self.st[idx] = self.A[l]
def build(self, idx, left, right):
if left == right:
self.st[idx] = self.A[left]
else:
mid = (l + r) // 2
self.build(self.left(idx), l, mid)
self.build(self.right(idx), mid + 1, r)
mid = (left + right) // 2
self.build(self.left(idx), left, mid)
self.build(self.right(idx), mid + 1, right)
self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)])
def update(self, a, b, val):
@ -56,18 +56,18 @@ class SegmentTree:
"""
return self.update_recursive(1, 0, self.N - 1, a - 1, b - 1, val)
def update_recursive(self, idx, l, r, a, b, val): # noqa: E741
def update_recursive(self, idx, left, right, a, b, val):
"""
update(1, 1, N, a, b, v) for update val v to [a,b]
"""
if r < a or l > b:
if right < a or left > b:
return True
if l == r:
if left == right:
self.st[idx] = val
return True
mid = (l + r) // 2
self.update_recursive(self.left(idx), l, mid, a, b, val)
self.update_recursive(self.right(idx), mid + 1, r, a, b, val)
mid = (left + right) // 2
self.update_recursive(self.left(idx), left, mid, a, b, val)
self.update_recursive(self.right(idx), mid + 1, right, a, b, val)
self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)])
return True
@ -83,22 +83,22 @@ class SegmentTree:
"""
return self.query_recursive(1, 0, self.N - 1, a - 1, b - 1)
def query_recursive(self, idx, l, r, a, b): # noqa: E741
def query_recursive(self, idx, left, right, a, b):
"""
query(1, 1, N, a, b) for query max of [a,b]
"""
if r < a or l > b:
if right < a or left > b:
return -math.inf
if l >= a and r <= b:
if left >= a and right <= b:
return self.st[idx]
mid = (l + r) // 2
q1 = self.query_recursive(self.left(idx), l, mid, a, b)
q2 = self.query_recursive(self.right(idx), mid + 1, r, a, b)
mid = (left + right) // 2
q1 = self.query_recursive(self.left(idx), left, mid, a, b)
q2 = self.query_recursive(self.right(idx), mid + 1, right, a, b)
return max(q1, q2)
def show_data(self):
show_list = []
for i in range(1, N + 1):
for i in range(1, self.N + 1):
show_list += [self.query(i, i)]
print(show_list)

View File

@ -13,7 +13,21 @@ from dataclasses import dataclass
@dataclass
class Node:
"""
A Node has data variable and pointers to Nodes to its left and right.
A Node represents an element of a binary tree, which contains:
Attributes:
data: The value stored in the node (int).
left: Pointer to the left child node (Node or None).
right: Pointer to the right child node (Node or None).
Example:
>>> node = Node(1, Node(2), Node(3))
>>> node.data
1
>>> node.left.data
2
>>> node.right.data
3
"""
data: int
@ -24,12 +38,25 @@ class Node:
def make_symmetric_tree() -> Node:
r"""
Create a symmetric tree for testing.
The tree looks like this:
1
/ \
2 2
/ \ / \
3 4 4 3
Returns:
Node: Root node of a symmetric tree.
Example:
>>> tree = make_symmetric_tree()
>>> tree.data
1
>>> tree.left.data == tree.right.data
True
>>> tree.left.left.data == tree.right.right.data
True
"""
root = Node(1)
root.left = Node(2)
@ -43,13 +70,26 @@ def make_symmetric_tree() -> Node:
def make_asymmetric_tree() -> Node:
r"""
Create a asymmetric tree for testing.
Create an asymmetric tree for testing.
The tree looks like this:
1
/ \
2 2
/ \ / \
3 4 3 4
Returns:
Node: Root node of an asymmetric tree.
Example:
>>> tree = make_asymmetric_tree()
>>> tree.data
1
>>> tree.left.data == tree.right.data
True
>>> tree.left.left.data == tree.right.right.data
False
"""
root = Node(1)
root.left = Node(2)
@ -63,7 +103,15 @@ def make_asymmetric_tree() -> Node:
def is_symmetric_tree(tree: Node) -> bool:
"""
Test cases for is_symmetric_tree function
Check if a binary tree is symmetric (i.e., a mirror of itself).
Parameters:
tree: The root node of the binary tree.
Returns:
bool: True if the tree is symmetric, False otherwise.
Example:
>>> is_symmetric_tree(make_symmetric_tree())
True
>>> is_symmetric_tree(make_asymmetric_tree())
@ -76,8 +124,17 @@ def is_symmetric_tree(tree: Node) -> bool:
def is_mirror(left: Node | None, right: Node | None) -> bool:
"""
Check if two subtrees are mirror images of each other.
Parameters:
left: The root node of the left subtree.
right: The root node of the right subtree.
Returns:
bool: True if the two subtrees are mirrors of each other, False otherwise.
Example:
>>> tree1 = make_symmetric_tree()
>>> tree1.right.right = Node(3)
>>> is_mirror(tree1.left, tree1.right)
True
>>> tree2 = make_asymmetric_tree()
@ -91,7 +148,7 @@ def is_mirror(left: Node | None, right: Node | None) -> bool:
# One side is empty while the other is not, which is not symmetric.
return False
if left.data == right.data:
# The values match, so check the subtree
# The values match, so check the subtrees recursively.
return is_mirror(left.left, right.right) and is_mirror(left.right, right.left)
return False

View File

@ -39,26 +39,23 @@ def split(root: Node | None, value: int) -> tuple[Node | None, Node | None]:
Left tree contains all values less than split value.
Right tree contains all values greater or equal, than split value
"""
if root is None: # None tree is split into 2 Nones
return None, None
elif root.value is None:
if root is None or root.value is None: # None tree is split into 2 Nones
return None, None
elif value < root.value:
"""
Right tree's root will be current node.
Now we split(with the same value) current node's left son
Left tree: left part of that split
Right tree's left son: right part of that split
"""
left, root.left = split(root.left, value)
return left, root
else:
if value < root.value:
"""
Right tree's root will be current node.
Now we split(with the same value) current node's left son
Left tree: left part of that split
Right tree's left son: right part of that split
"""
left, root.left = split(root.left, value)
return left, root
else:
"""
Just symmetric to previous case
"""
root.right, right = split(root.right, value)
return root, right
"""
Just symmetric to previous case
"""
root.right, right = split(root.right, value)
return root, right
def merge(left: Node | None, right: Node | None) -> Node | None:

View File

@ -1,4 +1,6 @@
#!/usr/bin/env python3
from abc import abstractmethod
from .number_theory.prime_numbers import next_prime
@ -173,6 +175,7 @@ class HashTable:
self.values[key] = data
self._keys[key] = data
@abstractmethod
def _collision_resolution(self, key, data=None):
"""
This method is a type of open addressing which is used for handling collision.

View File

@ -11,7 +11,7 @@ class QuadraticProbing(HashTable):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def _collision_resolution(self, key, data=None):
def _collision_resolution(self, key, data=None): # noqa: ARG002
"""
Quadratic probing is an open addressing scheme used for resolving
collisions in hash table.

View File

@ -73,7 +73,7 @@ class BinomialHeap:
30
Deleting - delete() test
>>> [first_heap.delete_min() for _ in range(20)]
>>> [int(first_heap.delete_min()) for _ in range(20)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Create a new Heap
@ -118,7 +118,7 @@ class BinomialHeap:
values in merged heap; (merge is inplace)
>>> results = []
>>> while not first_heap.is_empty():
... results.append(first_heap.delete_min())
... results.append(int(first_heap.delete_min()))
>>> results
[17, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 34]
"""
@ -354,7 +354,7 @@ class BinomialHeap:
# Merge heaps
self.merge_heaps(new_heap)
return min_value
return int(min_value)
def pre_order(self):
"""

View File

@ -38,13 +38,12 @@ class BinaryHeap:
def __swap_down(self, i: int) -> None:
"""Swap the element down"""
while self.__size >= 2 * i:
if 2 * i + 1 > self.__size:
if 2 * i + 1 > self.__size: # noqa: SIM114
bigger_child = 2 * i
elif self.__heap[2 * i] > self.__heap[2 * i + 1]:
bigger_child = 2 * i
else:
if self.__heap[2 * i] > self.__heap[2 * i + 1]:
bigger_child = 2 * i
else:
bigger_child = 2 * i + 1
bigger_child = 2 * i + 1
temporary = self.__heap[i]
if self.__heap[i] < self.__heap[bigger_child]:
self.__heap[i] = self.__heap[bigger_child]

View File

@ -66,14 +66,14 @@ class MinHeap:
# this is min-heapify method
def sift_down(self, idx, array):
while True:
l = self.get_left_child_idx(idx) # noqa: E741
r = self.get_right_child_idx(idx)
left = self.get_left_child_idx(idx)
right = self.get_right_child_idx(idx)
smallest = idx
if l < len(array) and array[l] < array[idx]:
smallest = l
if r < len(array) and array[r] < array[smallest]:
smallest = r
if left < len(array) and array[left] < array[idx]:
smallest = left
if right < len(array) and array[right] < array[smallest]:
smallest = right
if smallest != idx:
array[idx], array[smallest] = array[smallest], array[idx]

View File

View File

@ -0,0 +1,43 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11532
# https://github.com/TheAlgorithms/Python/pull/11532
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
from data_structures.kd_tree.kd_node import KDNode
def build_kdtree(points: list[list[float]], depth: int = 0) -> KDNode | None:
"""
Builds a KD-Tree from a list of points.
Args:
points: The list of points to build the KD-Tree from.
depth: The current depth in the tree
(used to determine axis for splitting).
Returns:
The root node of the KD-Tree,
or None if no points are provided.
"""
if not points:
return None
k = len(points[0]) # Dimensionality of the points
axis = depth % k
# Sort point list and choose median as pivot element
points.sort(key=lambda point: point[axis])
median_idx = len(points) // 2
# Create node and construct subtrees
left_points = points[:median_idx]
right_points = points[median_idx + 1 :]
return KDNode(
point=points[median_idx],
left=build_kdtree(left_points, depth + 1),
right=build_kdtree(right_points, depth + 1),
)

View File

@ -0,0 +1,46 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11532
# https://github.com/TheAlgorithms/Python/pull/11532
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
import numpy as np
from data_structures.kd_tree.build_kdtree import build_kdtree
from data_structures.kd_tree.example.hypercube_points import hypercube_points
from data_structures.kd_tree.nearest_neighbour_search import nearest_neighbour_search
def main() -> None:
"""
Demonstrates the use of KD-Tree by building it from random points
in a 10-dimensional hypercube and performing a nearest neighbor search.
"""
num_points: int = 5000
cube_size: float = 10.0 # Size of the hypercube (edge length)
num_dimensions: int = 10
# Generate random points within the hypercube
points: np.ndarray = hypercube_points(num_points, cube_size, num_dimensions)
hypercube_kdtree = build_kdtree(points.tolist())
# Generate a random query point within the same space
rng = np.random.default_rng()
query_point: list[float] = rng.random(num_dimensions).tolist()
# Perform nearest neighbor search
nearest_point, nearest_dist, nodes_visited = nearest_neighbour_search(
hypercube_kdtree, query_point
)
# Print the results
print(f"Query point: {query_point}")
print(f"Nearest point: {nearest_point}")
print(f"Distance: {nearest_dist:.4f}")
print(f"Nodes visited: {nodes_visited}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,29 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11532
# https://github.com/TheAlgorithms/Python/pull/11532
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
import numpy as np
def hypercube_points(
num_points: int, hypercube_size: float, num_dimensions: int
) -> np.ndarray:
"""
Generates random points uniformly distributed within an n-dimensional hypercube.
Args:
num_points: Number of points to generate.
hypercube_size: Size of the hypercube.
num_dimensions: Number of dimensions of the hypercube.
Returns:
An array of shape (num_points, num_dimensions)
with generated points.
"""
rng = np.random.default_rng()
shape = (num_points, num_dimensions)
return hypercube_size * rng.random(shape)

View File

@ -0,0 +1,38 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11532
# https://github.com/TheAlgorithms/Python/pull/11532
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
from __future__ import annotations
class KDNode:
"""
Represents a node in a KD-Tree.
Attributes:
point: The point stored in this node.
left: The left child node.
right: The right child node.
"""
def __init__(
self,
point: list[float],
left: KDNode | None = None,
right: KDNode | None = None,
) -> None:
"""
Initializes a KDNode with the given point and child nodes.
Args:
point (list[float]): The point stored in this node.
left (Optional[KDNode]): The left child node.
right (Optional[KDNode]): The right child node.
"""
self.point = point
self.left = left
self.right = right

View File

@ -0,0 +1,79 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11532
# https://github.com/TheAlgorithms/Python/pull/11532
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
from data_structures.kd_tree.kd_node import KDNode
def nearest_neighbour_search(
root: KDNode | None, query_point: list[float]
) -> tuple[list[float] | None, float, int]:
"""
Performs a nearest neighbor search in a KD-Tree for a given query point.
Args:
root (KDNode | None): The root node of the KD-Tree.
query_point (list[float]): The point for which the nearest neighbor
is being searched.
Returns:
tuple[list[float] | None, float, int]:
- The nearest point found in the KD-Tree to the query point,
or None if no point is found.
- The squared distance to the nearest point.
- The number of nodes visited during the search.
"""
nearest_point: list[float] | None = None
nearest_dist: float = float("inf")
nodes_visited: int = 0
def search(node: KDNode | None, depth: int = 0) -> None:
"""
Recursively searches for the nearest neighbor in the KD-Tree.
Args:
node: The current node in the KD-Tree.
depth: The current depth in the KD-Tree.
"""
nonlocal nearest_point, nearest_dist, nodes_visited
if node is None:
return
nodes_visited += 1
# Calculate the current distance (squared distance)
current_point = node.point
current_dist = sum(
(query_coord - point_coord) ** 2
for query_coord, point_coord in zip(query_point, current_point)
)
# Update nearest point if the current node is closer
if nearest_point is None or current_dist < nearest_dist:
nearest_point = current_point
nearest_dist = current_dist
# Determine which subtree to search first (based on axis and query point)
k = len(query_point) # Dimensionality of points
axis = depth % k
if query_point[axis] <= current_point[axis]:
nearer_subtree = node.left
further_subtree = node.right
else:
nearer_subtree = node.right
further_subtree = node.left
# Search the nearer subtree first
search(nearer_subtree, depth + 1)
# If the further subtree has a closer point
if (query_point[axis] - current_point[axis]) ** 2 < nearest_dist:
search(further_subtree, depth + 1)
search(root, 0)
return nearest_point, nearest_dist, nodes_visited

View File

@ -0,0 +1,108 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11532
# https://github.com/TheAlgorithms/Python/pull/11532
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
import numpy as np
import pytest
from data_structures.kd_tree.build_kdtree import build_kdtree
from data_structures.kd_tree.example.hypercube_points import hypercube_points
from data_structures.kd_tree.kd_node import KDNode
from data_structures.kd_tree.nearest_neighbour_search import nearest_neighbour_search
@pytest.mark.parametrize(
("num_points", "cube_size", "num_dimensions", "depth", "expected_result"),
[
(0, 10.0, 2, 0, None), # Empty points list
(10, 10.0, 2, 2, KDNode), # Depth = 2, 2D points
(10, 10.0, 3, -2, KDNode), # Depth = -2, 3D points
],
)
def test_build_kdtree(num_points, cube_size, num_dimensions, depth, expected_result):
"""
Test that KD-Tree is built correctly.
Cases:
- Empty points list.
- Positive depth value.
- Negative depth value.
"""
points = (
hypercube_points(num_points, cube_size, num_dimensions).tolist()
if num_points > 0
else []
)
kdtree = build_kdtree(points, depth=depth)
if expected_result is None:
# Empty points list case
assert kdtree is None, f"Expected None for empty points list, got {kdtree}"
else:
# Check if root node is not None
assert kdtree is not None, "Expected a KDNode, got None"
# Check if root has correct dimensions
assert (
len(kdtree.point) == num_dimensions
), f"Expected point dimension {num_dimensions}, got {len(kdtree.point)}"
# Check that the tree is balanced to some extent (simplistic check)
assert isinstance(
kdtree, KDNode
), f"Expected KDNode instance, got {type(kdtree)}"
def test_nearest_neighbour_search():
"""
Test the nearest neighbor search function.
"""
num_points = 10
cube_size = 10.0
num_dimensions = 2
points = hypercube_points(num_points, cube_size, num_dimensions)
kdtree = build_kdtree(points.tolist())
rng = np.random.default_rng()
query_point = rng.random(num_dimensions).tolist()
nearest_point, nearest_dist, nodes_visited = nearest_neighbour_search(
kdtree, query_point
)
# Check that nearest point is not None
assert nearest_point is not None
# Check that distance is a non-negative number
assert nearest_dist >= 0
# Check that nodes visited is a non-negative integer
assert nodes_visited >= 0
def test_edge_cases():
"""
Test edge cases such as an empty KD-Tree.
"""
empty_kdtree = build_kdtree([])
query_point = [0.0] * 2 # Using a default 2D query point
nearest_point, nearest_dist, nodes_visited = nearest_neighbour_search(
empty_kdtree, query_point
)
# With an empty KD-Tree, nearest_point should be None
assert nearest_point is None
assert nearest_dist == float("inf")
assert nodes_visited == 0
if __name__ == "__main__":
import pytest
pytest.main()

View File

@ -14,11 +14,11 @@ class Node:
def __iter__(self):
node = self
visited = []
visited = set()
while node:
if node in visited:
raise ContainsLoopError
visited.append(node)
visited.add(node)
yield node.data
node = node.next_node

View File

@ -63,7 +63,7 @@ def insert_node(head: Node | None, data: int) -> Node:
while temp_node.next_node:
temp_node = temp_node.next_node
temp_node.next_node = new_node # type: ignore
temp_node.next_node = new_node
return head

View File

@ -5,6 +5,7 @@ https://epaperpress.com/sortsearch/download/skiplist.pdf
from __future__ import annotations
from itertools import pairwise
from random import random
from typing import Generic, TypeVar
@ -389,7 +390,7 @@ def test_delete_doesnt_leave_dead_nodes():
def test_iter_always_yields_sorted_values():
def is_sorted(lst):
return all(next_item >= item for item, next_item in zip(lst, lst[1:]))
return all(next_item >= item for item, next_item in pairwise(lst))
skip_list = SkipList()
for i in range(10):

View File

@ -25,6 +25,7 @@ class CircularQueue:
def is_empty(self) -> bool:
"""
Checks whether the queue is empty or not
>>> cq = CircularQueue(5)
>>> cq.is_empty()
True
@ -35,6 +36,7 @@ class CircularQueue:
def first(self):
"""
Returns the first element of the queue
>>> cq = CircularQueue(5)
>>> cq.first()
False
@ -45,7 +47,8 @@ class CircularQueue:
def enqueue(self, data):
"""
This function insert an element in the queue using self.rear value as an index
This function inserts an element at the end of the queue using self.rear value
as an index.
>>> cq = CircularQueue(5)
>>> cq.enqueue("A") # doctest: +ELLIPSIS
<data_structures.queue.circular_queue.CircularQueue object at ...
@ -67,7 +70,7 @@ class CircularQueue:
def dequeue(self):
"""
This function removes an element from the queue using on self.front value as an
index
index and returns it
>>> cq = CircularQueue(5)
>>> cq.dequeue()
Traceback (most recent call last):

View File

@ -39,7 +39,7 @@ class CircularQueueLinkedList:
def is_empty(self) -> bool:
"""
Checks where the queue is empty or not
Checks whether the queue is empty or not
>>> cq = CircularQueueLinkedList()
>>> cq.is_empty()
True

View File

@ -19,9 +19,10 @@ def balanced_parentheses(parentheses: str) -> bool:
for bracket in parentheses:
if bracket in bracket_pairs:
stack.push(bracket)
elif bracket in (")", "]", "}"):
if stack.is_empty() or bracket_pairs[stack.pop()] != bracket:
return False
elif bracket in (")", "]", "}") and (
stack.is_empty() or bracket_pairs[stack.pop()] != bracket
):
return False
return stack.is_empty()

View File

@ -95,13 +95,12 @@ def infix_2_postfix(infix: str) -> str:
while stack[-1] != "(":
post_fix.append(stack.pop()) # Pop stack & add the content to Postfix
stack.pop()
else:
if len(stack) == 0:
stack.append(x) # If stack is empty, push x to stack
else: # while priority of x is not > priority of element in the stack
while stack and stack[-1] != "(" and priority[x] <= priority[stack[-1]]:
post_fix.append(stack.pop()) # pop stack & add to Postfix
stack.append(x) # push x to stack
elif len(stack) == 0:
stack.append(x) # If stack is empty, push x to stack
else: # while priority of x is not > priority of element in the stack
while stack and stack[-1] != "(" and priority[x] <= priority[stack[-1]]:
post_fix.append(stack.pop()) # pop stack & add to Postfix
stack.append(x) # push x to stack
print(
x.center(8),

View File

@ -0,0 +1,38 @@
from collections.abc import Iterator
def lexical_order(max_number: int) -> Iterator[int]:
"""
Generate numbers in lexical order from 1 to max_number.
>>> " ".join(map(str, lexical_order(13)))
'1 10 11 12 13 2 3 4 5 6 7 8 9'
>>> list(lexical_order(1))
[1]
>>> " ".join(map(str, lexical_order(20)))
'1 10 11 12 13 14 15 16 17 18 19 2 20 3 4 5 6 7 8 9'
>>> " ".join(map(str, lexical_order(25)))
'1 10 11 12 13 14 15 16 17 18 19 2 20 21 22 23 24 25 3 4 5 6 7 8 9'
>>> list(lexical_order(12))
[1, 10, 11, 12, 2, 3, 4, 5, 6, 7, 8, 9]
"""
stack = [1]
while stack:
num = stack.pop()
if num > max_number:
continue
yield num
if (num % 10) != 9:
stack.append(num + 1)
stack.append(num * 10)
if __name__ == "__main__":
from doctest import testmod
testmod()
print(f"Numbers from 1 to 25 in lexical order: {list(lexical_order(26))}")

View File

@ -6,9 +6,20 @@ expect = [-5, 0, 5, 5.1, 11, 13, 21, -1, 4, -1, -10, -5, -1, 0, -1]
def next_greatest_element_slow(arr: list[float]) -> list[float]:
"""
Get the Next Greatest Element (NGE) for all elements in a list.
Maximum element present after the current one which is also greater than the
current one.
Get the Next Greatest Element (NGE) for each element in the array
by checking all subsequent elements to find the next greater one.
This is a brute-force implementation, and it has a time complexity
of O(n^2), where n is the size of the array.
Args:
arr: List of numbers for which the NGE is calculated.
Returns:
List containing the next greatest elements. If no
greater element is found, -1 is placed in the result.
Example:
>>> next_greatest_element_slow(arr) == expect
True
"""
@ -28,9 +39,21 @@ def next_greatest_element_slow(arr: list[float]) -> list[float]:
def next_greatest_element_fast(arr: list[float]) -> list[float]:
"""
Like next_greatest_element_slow() but changes the loops to use
enumerate() instead of range(len()) for the outer loop and
for in a slice of arr for the inner loop.
Find the Next Greatest Element (NGE) for each element in the array
using a more readable approach. This implementation utilizes
enumerate() for the outer loop and slicing for the inner loop.
While this improves readability over next_greatest_element_slow(),
it still has a time complexity of O(n^2).
Args:
arr: List of numbers for which the NGE is calculated.
Returns:
List containing the next greatest elements. If no
greater element is found, -1 is placed in the result.
Example:
>>> next_greatest_element_fast(arr) == expect
True
"""
@ -47,14 +70,23 @@ def next_greatest_element_fast(arr: list[float]) -> list[float]:
def next_greatest_element(arr: list[float]) -> list[float]:
"""
Get the Next Greatest Element (NGE) for all elements in a list.
Maximum element present after the current one which is also greater than the
current one.
Efficient solution to find the Next Greatest Element (NGE) for all elements
using a stack. The time complexity is reduced to O(n), making it suitable
for larger arrays.
A naive way to solve this is to take two loops and check for the next bigger
number but that will make the time complexity as O(n^2). The better way to solve
this would be to use a stack to keep track of maximum number giving a linear time
solution.
The stack keeps track of elements for which the next greater element hasn't
been found yet. By iterating through the array in reverse (from the last
element to the first), the stack is used to efficiently determine the next
greatest element for each element.
Args:
arr: List of numbers for which the NGE is calculated.
Returns:
List containing the next greatest elements. If no
greater element is found, -1 is placed in the result.
Example:
>>> next_greatest_element(arr) == expect
True
"""

View File

View File

@ -0,0 +1,37 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11554
# https://github.com/TheAlgorithms/Python/pull/11554
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
from data_structures.suffix_tree.suffix_tree import SuffixTree
def main() -> None:
"""
Demonstrate the usage of the SuffixTree class.
- Initializes a SuffixTree with a predefined text.
- Defines a list of patterns to search for within the suffix tree.
- Searches for each pattern in the suffix tree.
Patterns tested:
- "ana" (found) --> True
- "ban" (found) --> True
- "na" (found) --> True
- "xyz" (not found) --> False
- "mon" (found) --> True
"""
text = "monkey banana"
suffix_tree = SuffixTree(text)
patterns = ["ana", "ban", "na", "xyz", "mon"]
for pattern in patterns:
found = suffix_tree.search(pattern)
print(f"Pattern '{pattern}' found: {found}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,66 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11554
# https://github.com/TheAlgorithms/Python/pull/11554
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
from data_structures.suffix_tree.suffix_tree_node import SuffixTreeNode
class SuffixTree:
def __init__(self, text: str) -> None:
"""
Initializes the suffix tree with the given text.
Args:
text (str): The text for which the suffix tree is to be built.
"""
self.text: str = text
self.root: SuffixTreeNode = SuffixTreeNode()
self.build_suffix_tree()
def build_suffix_tree(self) -> None:
"""
Builds the suffix tree for the given text by adding all suffixes.
"""
text = self.text
n = len(text)
for i in range(n):
suffix = text[i:]
self._add_suffix(suffix, i)
def _add_suffix(self, suffix: str, index: int) -> None:
"""
Adds a suffix to the suffix tree.
Args:
suffix (str): The suffix to add.
index (int): The starting index of the suffix in the original text.
"""
node = self.root
for char in suffix:
if char not in node.children:
node.children[char] = SuffixTreeNode()
node = node.children[char]
node.is_end_of_string = True
node.start = index
node.end = index + len(suffix) - 1
def search(self, pattern: str) -> bool:
"""
Searches for a pattern in the suffix tree.
Args:
pattern (str): The pattern to search for.
Returns:
bool: True if the pattern is found, False otherwise.
"""
node = self.root
for char in pattern:
if char not in node.children:
return False
node = node.children[char]
return True

View File

@ -0,0 +1,36 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11554
# https://github.com/TheAlgorithms/Python/pull/11554
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
from __future__ import annotations
class SuffixTreeNode:
def __init__(
self,
children: dict[str, SuffixTreeNode] | None = None,
is_end_of_string: bool = False,
start: int | None = None,
end: int | None = None,
suffix_link: SuffixTreeNode | None = None,
) -> None:
"""
Initializes a suffix tree node.
Parameters:
children (dict[str, SuffixTreeNode] | None): The children of this node.
is_end_of_string (bool): Indicates if this node represents
the end of a string.
start (int | None): The start index of the suffix in the text.
end (int | None): The end index of the suffix in the text.
suffix_link (SuffixTreeNode | None): Link to another suffix tree node.
"""
self.children = children or {}
self.is_end_of_string = is_end_of_string
self.start = start
self.end = end
self.suffix_link = suffix_link

View File

@ -0,0 +1,59 @@
# Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
# in Pull Request: #11554
# https://github.com/TheAlgorithms/Python/pull/11554
#
# Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request
# addressing bugs/corrections to this file.
# Thank you!
import unittest
from data_structures.suffix_tree.suffix_tree import SuffixTree
class TestSuffixTree(unittest.TestCase):
def setUp(self) -> None:
"""Set up the initial conditions for each test."""
self.text = "banana"
self.suffix_tree = SuffixTree(self.text)
def test_search_existing_patterns(self) -> None:
"""Test searching for patterns that exist in the suffix tree."""
patterns = ["ana", "ban", "na"]
for pattern in patterns:
with self.subTest(pattern=pattern):
assert self.suffix_tree.search(
pattern
), f"Pattern '{pattern}' should be found."
def test_search_non_existing_patterns(self) -> None:
"""Test searching for patterns that do not exist in the suffix tree."""
patterns = ["xyz", "apple", "cat"]
for pattern in patterns:
with self.subTest(pattern=pattern):
assert not self.suffix_tree.search(
pattern
), f"Pattern '{pattern}' should not be found."
def test_search_empty_pattern(self) -> None:
"""Test searching for an empty pattern."""
assert self.suffix_tree.search(""), "An empty pattern should be found."
def test_search_full_text(self) -> None:
"""Test searching for the full text."""
assert self.suffix_tree.search(
self.text
), "The full text should be found in the suffix tree."
def test_search_substrings(self) -> None:
"""Test searching for substrings of the full text."""
substrings = ["ban", "ana", "a", "na"]
for substring in substrings:
with self.subTest(substring=substring):
assert self.suffix_tree.search(
substring
), f"Substring '{substring}' should be found."
if __name__ == "__main__":
unittest.main()

View File

@ -153,31 +153,30 @@ class RadixNode:
# We have word remaining so we check the next node
elif remaining_word != "":
return incoming_node.delete(remaining_word)
# If it is not a leaf, we don't have to delete
elif not incoming_node.is_leaf:
return False
else:
# If it is not a leaf, we don't have to delete
if not incoming_node.is_leaf:
return False
# We delete the nodes if no edges go from it
if len(incoming_node.nodes) == 0:
del self.nodes[word[0]]
# We merge the current node with its only child
if len(self.nodes) == 1 and not self.is_leaf:
merging_node = next(iter(self.nodes.values()))
self.is_leaf = merging_node.is_leaf
self.prefix += merging_node.prefix
self.nodes = merging_node.nodes
# If there is more than 1 edge, we just mark it as non-leaf
elif len(incoming_node.nodes) > 1:
incoming_node.is_leaf = False
# If there is 1 edge, we merge it with its child
else:
# We delete the nodes if no edges go from it
if len(incoming_node.nodes) == 0:
del self.nodes[word[0]]
# We merge the current node with its only child
if len(self.nodes) == 1 and not self.is_leaf:
merging_node = next(iter(self.nodes.values()))
self.is_leaf = merging_node.is_leaf
self.prefix += merging_node.prefix
self.nodes = merging_node.nodes
# If there is more than 1 edge, we just mark it as non-leaf
elif len(incoming_node.nodes) > 1:
incoming_node.is_leaf = False
# If there is 1 edge, we merge it with its child
else:
merging_node = next(iter(incoming_node.nodes.values()))
incoming_node.is_leaf = merging_node.is_leaf
incoming_node.prefix += merging_node.prefix
incoming_node.nodes = merging_node.nodes
merging_node = next(iter(incoming_node.nodes.values()))
incoming_node.is_leaf = merging_node.is_leaf
incoming_node.prefix += merging_node.prefix
incoming_node.nodes = merging_node.nodes
return True
return True
def print_tree(self, height: int = 0) -> None:
"""Print the tree

View File

@ -74,9 +74,9 @@ def detect_high_low_threshold(
image_shape, destination, threshold_low, threshold_high, weak, strong
):
"""
High-Low threshold detection. If an edge pixels gradient value is higher
High-Low threshold detection. If an edge pixel's gradient value is higher
than the high threshold value, it is marked as a strong edge pixel. If an
edge pixels gradient value is smaller than the high threshold value and
edge pixel's gradient value is smaller than the high threshold value and
larger than the low threshold value, it is marked as a weak edge pixel. If
an edge pixel's value is smaller than the low threshold value, it will be
suppressed.

View File

@ -182,7 +182,7 @@ class IndexCalculation:
Atmospherically Resistant Vegetation Index 2
https://www.indexdatabase.de/db/i-single.php?id=396
:return: index
0.18+1.17*(self.nirself.red)/(self.nir+self.red)
-0.18+1.17*(self.nir-self.red)/(self.nir+self.red)
"""
return -0.18 + (1.17 * ((self.nir - self.red) / (self.nir + self.red)))

View File

@ -54,8 +54,7 @@ def dis_between_closest_pair(points, points_counts, min_dis=float("inf")):
for i in range(points_counts - 1):
for j in range(i + 1, points_counts):
current_dis = euclidean_distance_sqr(points[i], points[j])
if current_dis < min_dis:
min_dis = current_dis
min_dis = min(min_dis, current_dis)
return min_dis
@ -76,8 +75,7 @@ def dis_between_closest_in_strip(points, points_counts, min_dis=float("inf")):
for i in range(min(6, points_counts - 1), points_counts):
for j in range(max(0, i - 6), i):
current_dis = euclidean_distance_sqr(points[i], points[j])
if current_dis < min_dis:
min_dis = current_dis
min_dis = min(min_dis, current_dis)
return min_dis

View File

@ -274,14 +274,13 @@ def convex_hull_bf(points: list[Point]) -> list[Point]:
points_left_of_ij = True
elif det_k < 0:
points_right_of_ij = True
else:
# point[i], point[j], point[k] all lie on a straight line
# if point[k] is to the left of point[i] or it's to the
# right of point[j], then point[i], point[j] cannot be
# part of the convex hull of A
if points[k] < points[i] or points[k] > points[j]:
ij_part_of_convex_hull = False
break
# point[i], point[j], point[k] all lie on a straight line
# if point[k] is to the left of point[i] or it's to the
# right of point[j], then point[i], point[j] cannot be
# part of the convex hull of A
elif points[k] < points[i] or points[k] > points[j]:
ij_part_of_convex_hull = False
break
if points_left_of_ij and points_right_of_ij:
ij_part_of_convex_hull = False

View File

@ -2,6 +2,20 @@ def actual_power(a: int, b: int):
"""
Function using divide and conquer to calculate a^b.
It only works for integer a,b.
:param a: The base of the power operation, an integer.
:param b: The exponent of the power operation, a non-negative integer.
:return: The result of a^b.
Examples:
>>> actual_power(3, 2)
9
>>> actual_power(5, 3)
125
>>> actual_power(2, 5)
32
>>> actual_power(7, 0)
1
"""
if b == 0:
return 1
@ -13,6 +27,10 @@ def actual_power(a: int, b: int):
def power(a: int, b: int) -> float:
"""
:param a: The base (integer).
:param b: The exponent (integer).
:return: The result of a^b, as a float for negative exponents.
>>> power(4,6)
4096
>>> power(2,3)

0
docs/__init__.py Normal file
View File

3
docs/conf.py Normal file
View File

@ -0,0 +1,3 @@
from sphinx_pyproject import SphinxConfig
project = SphinxConfig("../pyproject.toml", globalns=globals()).name

View File

@ -18,7 +18,7 @@ Approach:
The basic idea is to go over recursively to find the way such that the sum
of chosen elements is tar. For every element, we have two choices
1. Include the element in our set of chosen elements.
2. Dont include the element in our set of chosen elements.
2. Don't include the element in our set of chosen elements.
"""

View File

@ -26,7 +26,7 @@ def _fib(n: int) -> tuple[int, int]:
if n == 0: # (F(0), F(1))
return (0, 1)
# F(2n) = F(n)[2F(n+1) F(n)]
# F(2n) = F(n)[2F(n+1) - F(n)]
# F(2n+1) = F(n+1)^2+F(n)^2
a, b = _fib(n // 2)
c = a * (b * 2 - a)

View File

@ -12,19 +12,58 @@ class Graph:
] # dp[i][j] stores minimum distance from i to j
def add_edge(self, u, v, w):
"""
Adds a directed edge from node u
to node v with weight w.
>>> g = Graph(3)
>>> g.add_edge(0, 1, 5)
>>> g.dp[0][1]
5
"""
self.dp[u][v] = w
def floyd_warshall(self):
"""
Computes the shortest paths between all pairs of
nodes using the Floyd-Warshall algorithm.
>>> g = Graph(3)
>>> g.add_edge(0, 1, 1)
>>> g.add_edge(1, 2, 2)
>>> g.floyd_warshall()
>>> g.show_min(0, 2)
3
>>> g.show_min(2, 0)
inf
"""
for k in range(self.n):
for i in range(self.n):
for j in range(self.n):
self.dp[i][j] = min(self.dp[i][j], self.dp[i][k] + self.dp[k][j])
def show_min(self, u, v):
"""
Returns the minimum distance from node u to node v.
>>> g = Graph(3)
>>> g.add_edge(0, 1, 3)
>>> g.add_edge(1, 2, 4)
>>> g.floyd_warshall()
>>> g.show_min(0, 2)
7
>>> g.show_min(1, 0)
inf
"""
return self.dp[u][v]
if __name__ == "__main__":
import doctest
doctest.testmod()
# Example usage
graph = Graph(5)
graph.add_edge(0, 2, 9)
graph.add_edge(0, 4, 10)
@ -38,5 +77,9 @@ if __name__ == "__main__":
graph.add_edge(4, 2, 4)
graph.add_edge(4, 3, 9)
graph.floyd_warshall()
graph.show_min(1, 4)
graph.show_min(0, 3)
print(
graph.show_min(1, 4)
) # Should output the minimum distance from node 1 to node 4
print(
graph.show_min(0, 3)
) # Should output the minimum distance from node 0 to node 3

View File

@ -28,6 +28,24 @@ def longest_common_subsequence(x: str, y: str):
(2, 'ph')
>>> longest_common_subsequence("computer", "food")
(1, 'o')
>>> longest_common_subsequence("", "abc") # One string is empty
(0, '')
>>> longest_common_subsequence("abc", "") # Other string is empty
(0, '')
>>> longest_common_subsequence("", "") # Both strings are empty
(0, '')
>>> longest_common_subsequence("abc", "def") # No common subsequence
(0, '')
>>> longest_common_subsequence("abc", "abc") # Identical strings
(3, 'abc')
>>> longest_common_subsequence("a", "a") # Single character match
(1, 'a')
>>> longest_common_subsequence("a", "b") # Single character no match
(0, '')
>>> longest_common_subsequence("abcdef", "ace") # Interleaved subsequence
(3, 'ace')
>>> longest_common_subsequence("ABCD", "ACBD") # No repeated characters
(3, 'ABD')
"""
# find the length of strings
@ -38,30 +56,30 @@ def longest_common_subsequence(x: str, y: str):
n = len(y)
# declaring the array for storing the dp values
l = [[0] * (n + 1) for _ in range(m + 1)] # noqa: E741
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
match = 1 if x[i - 1] == y[j - 1] else 0
l[i][j] = max(l[i - 1][j], l[i][j - 1], l[i - 1][j - 1] + match)
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1] + match)
seq = ""
i, j = m, n
while i > 0 and j > 0:
match = 1 if x[i - 1] == y[j - 1] else 0
if l[i][j] == l[i - 1][j - 1] + match:
if dp[i][j] == dp[i - 1][j - 1] + match:
if match == 1:
seq = x[i - 1] + seq
i -= 1
j -= 1
elif l[i][j] == l[i - 1][j]:
elif dp[i][j] == dp[i - 1][j]:
i -= 1
else:
j -= 1
return l[m][n], seq
return dp[m][n], seq
if __name__ == "__main__":

View File

@ -7,14 +7,14 @@
from __future__ import annotations
def ceil_index(v, l, r, key): # noqa: E741
while r - l > 1:
m = (l + r) // 2
if v[m] >= key:
r = m
def ceil_index(v, left, right, key):
while right - left > 1:
middle = (left + right) // 2
if v[middle] >= key:
right = middle
else:
l = m # noqa: E741
return r
left = middle
return right
def longest_increasing_subsequence_length(v: list[int]) -> int:

View File

@ -45,7 +45,7 @@ def subset_combinations(elements: list[int], n: int) -> list:
for i in range(1, r + 1):
for j in range(i, 0, -1):
for prev_combination in dp[j - 1]:
dp[j].append(tuple(prev_combination) + (elements[i - 1],))
dp[j].append((*prev_combination, elements[i - 1]))
try:
return sorted(dp[n])

0
electronics/__init__.py Normal file
View File

View File

@ -37,10 +37,9 @@ class CircularConvolution:
using matrix method
Usage:
>>> import circular_convolution as cc
>>> convolution = cc.CircularConvolution()
>>> convolution = CircularConvolution()
>>> convolution.circular_convolution()
[10, 10, 6, 14]
[10.0, 10.0, 6.0, 14.0]
>>> convolution.first_signal = [0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6]
>>> convolution.second_signal = [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3, 1.5]
@ -55,7 +54,7 @@ class CircularConvolution:
>>> convolution.first_signal = [1, -1, 2, 3, -1]
>>> convolution.second_signal = [1, 2, 3]
>>> convolution.circular_convolution()
[8, -2, 3, 4, 11]
[8.0, -2.0, 3.0, 4.0, 11.0]
"""
@ -92,7 +91,7 @@ class CircularConvolution:
final_signal = np.matmul(np.transpose(matrix), np.transpose(self.first_signal))
# rounding-off to two decimal places
return [round(i, 2) for i in final_signal]
return [float(round(i, 2)) for i in final_signal]
if __name__ == "__main__":

View File

@ -20,8 +20,8 @@ def couloumbs_law(
Reference
----------
Coulomb (1785) "Premier mémoire sur lélectricité et le magnétisme,"
Histoire de lAcadémie Royale des Sciences, pp. 569577.
Coulomb (1785) "Premier mémoire sur l'électricité et le magnétisme,"
Histoire de l'Académie Royale des Sciences, pp. 569-577.
Parameters
----------

View File

@ -1,4 +1,4 @@
### Interest
# Interest
* Compound Interest: "Compound interest is calculated by multiplying the initial principal amount by one plus the annual interest rate raised to the number of compound periods minus one." [Compound Interest](https://www.investopedia.com/)
* Simple Interest: "Simple interest paid or received over a certain period is a fixed percentage of the principal amount that was borrowed or lent. " [Simple Interest](https://www.investopedia.com/)

0
fractals/__init__.py Normal file
View File

View File

@ -25,8 +25,8 @@ import warnings
from collections.abc import Callable
from typing import Any
import numpy
from matplotlib import pyplot
import matplotlib.pyplot as plt
import numpy as np
c_cauliflower = 0.25 + 0.0j
c_polynomial_1 = -0.4 + 0.6j
@ -37,22 +37,20 @@ window_size = 2.0
nb_pixels = 666
def eval_exponential(c_parameter: complex, z_values: numpy.ndarray) -> numpy.ndarray:
def eval_exponential(c_parameter: complex, z_values: np.ndarray) -> np.ndarray:
"""
Evaluate $e^z + c$.
>>> eval_exponential(0, 0)
>>> float(eval_exponential(0, 0))
1.0
>>> abs(eval_exponential(1, numpy.pi*1.j)) < 1e-15
>>> bool(abs(eval_exponential(1, np.pi*1.j)) < 1e-15)
True
>>> abs(eval_exponential(1.j, 0)-1-1.j) < 1e-15
>>> bool(abs(eval_exponential(1.j, 0)-1-1.j) < 1e-15)
True
"""
return numpy.exp(z_values) + c_parameter
return np.exp(z_values) + c_parameter
def eval_quadratic_polynomial(
c_parameter: complex, z_values: numpy.ndarray
) -> numpy.ndarray:
def eval_quadratic_polynomial(c_parameter: complex, z_values: np.ndarray) -> np.ndarray:
"""
>>> eval_quadratic_polynomial(0, 2)
4
@ -66,7 +64,7 @@ def eval_quadratic_polynomial(
return z_values * z_values + c_parameter
def prepare_grid(window_size: float, nb_pixels: int) -> numpy.ndarray:
def prepare_grid(window_size: float, nb_pixels: int) -> np.ndarray:
"""
Create a grid of complex values of size nb_pixels*nb_pixels with real and
imaginary parts ranging from -window_size to window_size (inclusive).
@ -77,20 +75,20 @@ def prepare_grid(window_size: float, nb_pixels: int) -> numpy.ndarray:
[ 0.-1.j, 0.+0.j, 0.+1.j],
[ 1.-1.j, 1.+0.j, 1.+1.j]])
"""
x = numpy.linspace(-window_size, window_size, nb_pixels)
x = np.linspace(-window_size, window_size, nb_pixels)
x = x.reshape((nb_pixels, 1))
y = numpy.linspace(-window_size, window_size, nb_pixels)
y = np.linspace(-window_size, window_size, nb_pixels)
y = y.reshape((1, nb_pixels))
return x + 1.0j * y
def iterate_function(
eval_function: Callable[[Any, numpy.ndarray], numpy.ndarray],
eval_function: Callable[[Any, np.ndarray], np.ndarray],
function_params: Any,
nb_iterations: int,
z_0: numpy.ndarray,
z_0: np.ndarray,
infinity: float | None = None,
) -> numpy.ndarray:
) -> np.ndarray:
"""
Iterate the function "eval_function" exactly nb_iterations times.
The first argument of the function is a parameter which is contained in
@ -98,22 +96,22 @@ def iterate_function(
values to iterate from.
This function returns the final iterates.
>>> iterate_function(eval_quadratic_polynomial, 0, 3, numpy.array([0,1,2])).shape
>>> iterate_function(eval_quadratic_polynomial, 0, 3, np.array([0,1,2])).shape
(3,)
>>> numpy.round(iterate_function(eval_quadratic_polynomial,
>>> complex(np.round(iterate_function(eval_quadratic_polynomial,
... 0,
... 3,
... numpy.array([0,1,2]))[0])
... np.array([0,1,2]))[0]))
0j
>>> numpy.round(iterate_function(eval_quadratic_polynomial,
>>> complex(np.round(iterate_function(eval_quadratic_polynomial,
... 0,
... 3,
... numpy.array([0,1,2]))[1])
... np.array([0,1,2]))[1]))
(1+0j)
>>> numpy.round(iterate_function(eval_quadratic_polynomial,
>>> complex(np.round(iterate_function(eval_quadratic_polynomial,
... 0,
... 3,
... numpy.array([0,1,2]))[2])
... np.array([0,1,2]))[2]))
(256+0j)
"""
@ -121,8 +119,8 @@ def iterate_function(
for _ in range(nb_iterations):
z_n = eval_function(function_params, z_n)
if infinity is not None:
numpy.nan_to_num(z_n, copy=False, nan=infinity)
z_n[abs(z_n) == numpy.inf] = infinity
np.nan_to_num(z_n, copy=False, nan=infinity)
z_n[abs(z_n) == np.inf] = infinity
return z_n
@ -130,21 +128,21 @@ def show_results(
function_label: str,
function_params: Any,
escape_radius: float,
z_final: numpy.ndarray,
z_final: np.ndarray,
) -> None:
"""
Plots of whether the absolute value of z_final is greater than
the value of escape_radius. Adds the function_label and function_params to
the title.
>>> show_results('80', 0, 1, numpy.array([[0,1,.5],[.4,2,1.1],[.2,1,1.3]]))
>>> show_results('80', 0, 1, np.array([[0,1,.5],[.4,2,1.1],[.2,1,1.3]]))
"""
abs_z_final = (abs(z_final)).transpose()
abs_z_final[:, :] = abs_z_final[::-1, :]
pyplot.matshow(abs_z_final < escape_radius)
pyplot.title(f"Julia set of ${function_label}$, $c={function_params}$")
pyplot.show()
plt.matshow(abs_z_final < escape_radius)
plt.title(f"Julia set of ${function_label}$, $c={function_params}$")
plt.show()
def ignore_overflow_warnings() -> None:

View File

@ -22,25 +22,25 @@ Requirements (pip):
from __future__ import annotations
import matplotlib.pyplot as plt # type: ignore
import numpy
import matplotlib.pyplot as plt
import numpy as np
# initial triangle of Koch snowflake
VECTOR_1 = numpy.array([0, 0])
VECTOR_2 = numpy.array([0.5, 0.8660254])
VECTOR_3 = numpy.array([1, 0])
VECTOR_1 = np.array([0, 0])
VECTOR_2 = np.array([0.5, 0.8660254])
VECTOR_3 = np.array([1, 0])
INITIAL_VECTORS = [VECTOR_1, VECTOR_2, VECTOR_3, VECTOR_1]
# uncomment for simple Koch curve instead of Koch snowflake
# INITIAL_VECTORS = [VECTOR_1, VECTOR_3]
def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndarray]:
def iterate(initial_vectors: list[np.ndarray], steps: int) -> list[np.ndarray]:
"""
Go through the number of iterations determined by the argument "steps".
Be careful with high values (above 5) since the time to calculate increases
exponentially.
>>> iterate([numpy.array([0, 0]), numpy.array([1, 0])], 1)
>>> iterate([np.array([0, 0]), np.array([1, 0])], 1)
[array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \
0.28867513]), array([0.66666667, 0. ]), array([1, 0])]
"""
@ -50,13 +50,13 @@ def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndar
return vectors
def iteration_step(vectors: list[numpy.ndarray]) -> list[numpy.ndarray]:
def iteration_step(vectors: list[np.ndarray]) -> list[np.ndarray]:
"""
Loops through each pair of adjacent vectors. Each line between two adjacent
vectors is divided into 4 segments by adding 3 additional vectors in-between
the original two vectors. The vector in the middle is constructed through a
60 degree rotation so it is bent outwards.
>>> iteration_step([numpy.array([0, 0]), numpy.array([1, 0])])
>>> iteration_step([np.array([0, 0]), np.array([1, 0])])
[array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \
0.28867513]), array([0.66666667, 0. ]), array([1, 0])]
"""
@ -74,22 +74,22 @@ def iteration_step(vectors: list[numpy.ndarray]) -> list[numpy.ndarray]:
return new_vectors
def rotate(vector: numpy.ndarray, angle_in_degrees: float) -> numpy.ndarray:
def rotate(vector: np.ndarray, angle_in_degrees: float) -> np.ndarray:
"""
Standard rotation of a 2D vector with a rotation matrix
(see https://en.wikipedia.org/wiki/Rotation_matrix )
>>> rotate(numpy.array([1, 0]), 60)
>>> rotate(np.array([1, 0]), 60)
array([0.5 , 0.8660254])
>>> rotate(numpy.array([1, 0]), 90)
>>> rotate(np.array([1, 0]), 90)
array([6.123234e-17, 1.000000e+00])
"""
theta = numpy.radians(angle_in_degrees)
c, s = numpy.cos(theta), numpy.sin(theta)
rotation_matrix = numpy.array(((c, -s), (s, c)))
return numpy.dot(rotation_matrix, vector)
theta = np.radians(angle_in_degrees)
c, s = np.cos(theta), np.sin(theta)
rotation_matrix = np.array(((c, -s), (s, c)))
return np.dot(rotation_matrix, vector)
def plot(vectors: list[numpy.ndarray]) -> None:
def plot(vectors: list[np.ndarray]) -> None:
"""
Utility function to plot the vectors using matplotlib.pyplot
No doctest was implemented since this function does not have a return value

Some files were not shown because too many files have changed in this diff Show More