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 # 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} FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
COPY requirements.txt /tmp/pip-tmp/ COPY requirements.txt /tmp/pip-tmp/
RUN python3 -m pip install --upgrade pip \ 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 // 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. // Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon. // 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 /.* @cclauss
# /arithmetic_analysis/
# /backtracking/ # /backtracking/
# /bit_manipulation/ # /bit_manipulation/

View File

@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: 3.12 python-version: 3.13
allow-prereleases: true allow-prereleases: true
- uses: actions/cache@v4 - uses: actions/cache@v4
with: with:
@ -20,13 +20,18 @@ jobs:
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
- name: Install dependencies - name: Install dependencies
run: | 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 python -m pip install pytest-cov -r requirements.txt
- name: Run tests - name: Run tests
# TODO: #8818 Re-enable quantum tests # TODO: #8818 Re-enable quantum tests
run: pytest 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=project_euler/
--ignore=quantum/q_fourier_transform.py
--ignore=scripts/validate_solutions.py --ignore=scripts/validate_solutions.py
--cov-report=term-missing:skip-covered --cov-report=term-missing:skip-covered
--cov=. . --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: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0 rev: v5.0.0
hooks: hooks:
- id: check-executables-have-shebangs - id: check-executables-have-shebangs
- id: check-toml - id: check-toml
@ -11,25 +11,25 @@ repos:
- id: requirements-txt-fixer - id: requirements-txt-fixer
- repo: https://github.com/MarcoGorelli/auto-walrus - repo: https://github.com/MarcoGorelli/auto-walrus
rev: v0.2.2 rev: 0.3.4
hooks: hooks:
- id: auto-walrus - id: auto-walrus
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4 rev: v0.7.4
hooks: hooks:
- id: ruff - id: ruff
- id: ruff-format - id: ruff-format
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.2.6 rev: v2.3.0
hooks: hooks:
- id: codespell - id: codespell
additional_dependencies: additional_dependencies:
- tomli - tomli
- repo: https://github.com/tox-dev/pyproject-fmt - repo: https://github.com/tox-dev/pyproject-fmt
rev: "1.7.0" rev: "v2.5.0"
hooks: hooks:
- id: pyproject-fmt - id: pyproject-fmt
@ -42,15 +42,16 @@ repos:
pass_filenames: false pass_filenames: false
- repo: https://github.com/abravalheri/validate-pyproject - repo: https://github.com/abravalheri/validate-pyproject
rev: v0.16 rev: v0.23
hooks: hooks:
- id: validate-pyproject - id: validate-pyproject
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.9.0 rev: v1.13.0
hooks: hooks:
- id: mypy - id: mypy
args: args:
- --explicit-package-bases
- --ignore-missing-imports - --ignore-missing-imports
- --install-types # See mirrors-mypy README.md - --install-types # See mirrors-mypy README.md
- --non-interactive - --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: 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. - 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. - 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. - 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 ```bash
python3 -m pip install ruff # only required the first time python3 -m pip install ruff # only required the first time
ruff . ruff check
``` ```
- Original code submission require docstrings or comments to describe your work. - 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) * [Rat In Maze](backtracking/rat_in_maze.py)
* [Sudoku](backtracking/sudoku.py) * [Sudoku](backtracking/sudoku.py)
* [Sum Of Subsets](backtracking/sum_of_subsets.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) * [Word Search](backtracking/word_search.py)
## Bit Manipulation ## Bit Manipulation
@ -98,6 +100,7 @@
* [Elgamal Key Generator](ciphers/elgamal_key_generator.py) * [Elgamal Key Generator](ciphers/elgamal_key_generator.py)
* [Enigma Machine2](ciphers/enigma_machine2.py) * [Enigma Machine2](ciphers/enigma_machine2.py)
* [Fractionated Morse Cipher](ciphers/fractionated_morse_cipher.py) * [Fractionated Morse Cipher](ciphers/fractionated_morse_cipher.py)
* [Gronsfeld Cipher](ciphers/gronsfeld_cipher.py)
* [Hill Cipher](ciphers/hill_cipher.py) * [Hill Cipher](ciphers/hill_cipher.py)
* [Mixed Keyword Cypher](ciphers/mixed_keyword_cypher.py) * [Mixed Keyword Cypher](ciphers/mixed_keyword_cypher.py)
* [Mono Alphabetic Ciphers](ciphers/mono_alphabetic_ciphers.py) * [Mono Alphabetic Ciphers](ciphers/mono_alphabetic_ciphers.py)
@ -210,6 +213,7 @@
* [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py) * [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py)
* [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py) * [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py)
* [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.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) * [Merge Two Binary Trees](data_structures/binary_tree/merge_two_binary_trees.py)
* [Mirror Binary Tree](data_structures/binary_tree/mirror_binary_tree.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) * [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) * [Min Heap](data_structures/heap/min_heap.py)
* [Randomized Heap](data_structures/heap/randomized_heap.py) * [Randomized Heap](data_structures/heap/randomized_heap.py)
* [Skew Heap](data_structures/heap/skew_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 * Linked List
* [Circular Linked List](data_structures/linked_list/circular_linked_list.py) * [Circular Linked List](data_structures/linked_list/circular_linked_list.py)
* [Deque Doubly](data_structures/linked_list/deque_doubly.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) * [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 Postfix Conversion](data_structures/stacks/infix_to_postfix_conversion.py)
* [Infix To Prefix Conversion](data_structures/stacks/infix_to_prefix_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) * [Next Greater Element](data_structures/stacks/next_greater_element.py)
* [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py) * [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py)
* [Prefix Evaluation](data_structures/stacks/prefix_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 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) * [Stack With Singly Linked List](data_structures/stacks/stack_with_singly_linked_list.py)
* [Stock Span Problem](data_structures/stacks/stock_span_problem.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 * Trie
* [Radix Tree](data_structures/trie/radix_tree.py) * [Radix Tree](data_structures/trie/radix_tree.py)
* [Trie](data_structures/trie/trie.py) * [Trie](data_structures/trie/trie.py)
@ -330,6 +351,9 @@
* [Power](divide_and_conquer/power.py) * [Power](divide_and_conquer/power.py)
* [Strassen Matrix Multiplication](divide_and_conquer/strassen_matrix_multiplication.py) * [Strassen Matrix Multiplication](divide_and_conquer/strassen_matrix_multiplication.py)
## Docs
* [Conf](docs/conf.py)
## Dynamic Programming ## Dynamic Programming
* [Abbreviation](dynamic_programming/abbreviation.py) * [Abbreviation](dynamic_programming/abbreviation.py)
* [All Construct](dynamic_programming/all_construct.py) * [All Construct](dynamic_programming/all_construct.py)
@ -351,7 +375,7 @@
* [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py) * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py)
* [Longest Common Substring](dynamic_programming/longest_common_substring.py) * [Longest Common Substring](dynamic_programming/longest_common_substring.py)
* [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.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) * [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py)
* [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py) * [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py)
* [Matrix Chain Order](dynamic_programming/matrix_chain_order.py) * [Matrix Chain Order](dynamic_programming/matrix_chain_order.py)
@ -465,7 +489,7 @@
* [Dijkstra Alternate](graphs/dijkstra_alternate.py) * [Dijkstra Alternate](graphs/dijkstra_alternate.py)
* [Dijkstra Binary Grid](graphs/dijkstra_binary_grid.py) * [Dijkstra Binary Grid](graphs/dijkstra_binary_grid.py)
* [Dinic](graphs/dinic.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) * [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) * [Eulerian Path And Circuit For Undirected Graph](graphs/eulerian_path_and_circuit_for_undirected_graph.py)
* [Even Tree](graphs/even_tree.py) * [Even Tree](graphs/even_tree.py)
@ -540,8 +564,7 @@
* [Lu Decomposition](linear_algebra/lu_decomposition.py) * [Lu Decomposition](linear_algebra/lu_decomposition.py)
* Src * Src
* [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py) * [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py)
* Gaussian Elimination Pivoting * [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting.py)
* [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py)
* [Lib](linear_algebra/src/lib.py) * [Lib](linear_algebra/src/lib.py)
* [Polynom For Points](linear_algebra/src/polynom_for_points.py) * [Polynom For Points](linear_algebra/src/polynom_for_points.py)
* [Power Iteration](linear_algebra/src/power_iteration.py) * [Power Iteration](linear_algebra/src/power_iteration.py)
@ -661,7 +684,6 @@
* [Manhattan Distance](maths/manhattan_distance.py) * [Manhattan Distance](maths/manhattan_distance.py)
* [Matrix Exponentiation](maths/matrix_exponentiation.py) * [Matrix Exponentiation](maths/matrix_exponentiation.py)
* [Max Sum Sliding Window](maths/max_sum_sliding_window.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) * [Minkowski Distance](maths/minkowski_distance.py)
* [Mobius Function](maths/mobius_function.py) * [Mobius Function](maths/mobius_function.py)
* [Modular Division](maths/modular_division.py) * [Modular Division](maths/modular_division.py)
@ -773,6 +795,7 @@
* [Inverse Of Matrix](matrix/inverse_of_matrix.py) * [Inverse Of Matrix](matrix/inverse_of_matrix.py)
* [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py) * [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py)
* [Matrix Class](matrix/matrix_class.py) * [Matrix Class](matrix/matrix_class.py)
* [Matrix Equalization](matrix/matrix_equalization.py)
* [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py) * [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py)
* [Matrix Operation](matrix/matrix_operation.py) * [Matrix Operation](matrix/matrix_operation.py)
* [Max Area Of Island](matrix/max_area_of_island.py) * [Max Area Of Island](matrix/max_area_of_island.py)
@ -792,7 +815,6 @@
* [Minimum Cut](networking_flow/minimum_cut.py) * [Minimum Cut](networking_flow/minimum_cut.py)
## Neural Network ## Neural Network
* [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py)
* Activation Functions * Activation Functions
* [Binary Step](neural_network/activation_functions/binary_step.py) * [Binary Step](neural_network/activation_functions/binary_step.py)
* [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.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) * [Convolution Neural Network](neural_network/convolution_neural_network.py)
* [Input Data](neural_network/input_data.py) * [Input Data](neural_network/input_data.py)
* [Simple Neural Network](neural_network/simple_neural_network.py) * [Simple Neural Network](neural_network/simple_neural_network.py)
* [Two Hidden Layers Neural Network](neural_network/two_hidden_layers_neural_network.py)
## Other ## Other
* [Activity Selection](other/activity_selection.py) * [Activity Selection](other/activity_selection.py)
@ -863,6 +886,7 @@
* [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py) * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py)
* [Photoelectric Effect](physics/photoelectric_effect.py) * [Photoelectric Effect](physics/photoelectric_effect.py)
* [Potential Energy](physics/potential_energy.py) * [Potential Energy](physics/potential_energy.py)
* [Rainfall Intensity](physics/rainfall_intensity.py)
* [Reynolds Number](physics/reynolds_number.py) * [Reynolds Number](physics/reynolds_number.py)
* [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py)
* [Shear Stress](physics/shear_stress.py) * [Shear Stress](physics/shear_stress.py)
@ -1184,6 +1208,7 @@
* [Binary Tree Traversal](searches/binary_tree_traversal.py) * [Binary Tree Traversal](searches/binary_tree_traversal.py)
* [Double Linear Search](searches/double_linear_search.py) * [Double Linear Search](searches/double_linear_search.py)
* [Double Linear Search Recursion](searches/double_linear_search_recursion.py) * [Double Linear Search Recursion](searches/double_linear_search_recursion.py)
* [Exponential Search](searches/exponential_search.py)
* [Fibonacci Search](searches/fibonacci_search.py) * [Fibonacci Search](searches/fibonacci_search.py)
* [Hill Climbing](searches/hill_climbing.py) * [Hill Climbing](searches/hill_climbing.py)
* [Interpolation Search](searches/interpolation_search.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) * [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py)
* [Capitalize](strings/capitalize.py) * [Capitalize](strings/capitalize.py)
* [Check Anagrams](strings/check_anagrams.py) * [Check Anagrams](strings/check_anagrams.py)
* [Count Vowels](strings/count_vowels.py)
* [Credit Card Validator](strings/credit_card_validator.py) * [Credit Card Validator](strings/credit_card_validator.py)
* [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py) * [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py)
* [Detecting English Programmatically](strings/detecting_english_programmatically.py) * [Detecting English Programmatically](strings/detecting_english_programmatically.py)
@ -1326,7 +1352,6 @@
* [Get Ip Geolocation](web_programming/get_ip_geolocation.py) * [Get Ip Geolocation](web_programming/get_ip_geolocation.py)
* [Get Top Billionaires](web_programming/get_top_billionaires.py) * [Get Top Billionaires](web_programming/get_top_billionaires.py)
* [Get Top Hn Posts](web_programming/get_top_hn_posts.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) * [Giphy](web_programming/giphy.py)
* [Instagram Crawler](web_programming/instagram_crawler.py) * [Instagram Crawler](web_programming/instagram_crawler.py)
* [Instagram Pic](web_programming/instagram_pic.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 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( def make_lowpass(
frequency: int, frequency: int,
samplerate: int, samplerate: int,
q_factor: float = 1 / sqrt(2), # noqa: B008 q_factor: float = 1 / sqrt(2),
) -> IIRFilter: ) -> IIRFilter:
""" """
Creates a low-pass filter Creates a low-pass filter
@ -43,7 +43,7 @@ def make_lowpass(
def make_highpass( def make_highpass(
frequency: int, frequency: int,
samplerate: int, samplerate: int,
q_factor: float = 1 / sqrt(2), # noqa: B008 q_factor: float = 1 / sqrt(2),
) -> IIRFilter: ) -> IIRFilter:
""" """
Creates a high-pass filter Creates a high-pass filter
@ -73,7 +73,7 @@ def make_highpass(
def make_bandpass( def make_bandpass(
frequency: int, frequency: int,
samplerate: int, samplerate: int,
q_factor: float = 1 / sqrt(2), # noqa: B008 q_factor: float = 1 / sqrt(2),
) -> IIRFilter: ) -> IIRFilter:
""" """
Creates a band-pass filter Creates a band-pass filter
@ -104,7 +104,7 @@ def make_bandpass(
def make_allpass( def make_allpass(
frequency: int, frequency: int,
samplerate: int, samplerate: int,
q_factor: float = 1 / sqrt(2), # noqa: B008 q_factor: float = 1 / sqrt(2),
) -> IIRFilter: ) -> IIRFilter:
""" """
Creates an all-pass filter Creates an all-pass filter
@ -132,7 +132,7 @@ def make_peak(
frequency: int, frequency: int,
samplerate: int, samplerate: int,
gain_db: float, gain_db: float,
q_factor: float = 1 / sqrt(2), # noqa: B008 q_factor: float = 1 / sqrt(2),
) -> IIRFilter: ) -> IIRFilter:
""" """
Creates a peak filter Creates a peak filter
@ -164,7 +164,7 @@ def make_lowshelf(
frequency: int, frequency: int,
samplerate: int, samplerate: int,
gain_db: float, gain_db: float,
q_factor: float = 1 / sqrt(2), # noqa: B008 q_factor: float = 1 / sqrt(2),
) -> IIRFilter: ) -> IIRFilter:
""" """
Creates a low-shelf filter Creates a low-shelf filter
@ -201,7 +201,7 @@ def make_highshelf(
frequency: int, frequency: int,
samplerate: int, samplerate: int,
gain_db: float, gain_db: float,
q_factor: float = 1 / sqrt(2), # noqa: B008 q_factor: float = 1 / sqrt(2),
) -> IIRFilter: ) -> IIRFilter:
""" """
Creates a high-shelf filter Creates a high-shelf filter

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod
from math import pi from math import pi
from typing import Protocol from typing import Protocol
@ -8,6 +9,7 @@ import numpy as np
class FilterType(Protocol): class FilterType(Protocol):
@abstractmethod
def process(self, sample: float) -> float: def process(self, sample: float) -> float:
""" """
Calculate y[n] Calculate y[n]
@ -15,7 +17,6 @@ class FilterType(Protocol):
>>> issubclass(FilterType, Protocol) >>> issubclass(FilterType, Protocol)
True True
""" """
return 0.0
def get_bounds( 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. Creates a state space tree to iterate through each branch using DFS.
We know that each state has exactly len(sequence) - index children. We know that each state has exactly len(sequence) - index children.
It terminates when it reaches the end of the given sequence. 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): 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. Creates a state space tree to iterate through each branch using DFS.
We know that each state has exactly two children. We know that each state has exactly two children.
It terminates when it reaches the end of the given sequence. 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): if index == len(sequence):
@ -35,7 +85,7 @@ def create_state_space_tree(
if __name__ == "__main__": if __name__ == "__main__":
seq: list[Any] = [3, 1, 2, 4] seq: list[Any] = [1, 2, 3]
generate_all_subsequences(seq) generate_all_subsequences(seq)
seq.clear() seq.clear()

View File

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

View File

@ -24,10 +24,10 @@ def get_valid_pos(position: tuple[int, int], n: int) -> list[tuple[int, int]]:
] ]
permissible_positions = [] permissible_positions = []
for position in positions: for inner_position in positions:
y_test, x_test = position y_test, x_test = inner_position
if 0 <= y_test < n and 0 <= x_test < n: if 0 <= y_test < n and 0 <= x_test < n:
permissible_positions.append(position) permissible_positions.append(inner_position)
return permissible_positions 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 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. 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 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) >>> binary_and(0, 1.1)
Traceback (most recent call last): 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") >>> binary_and("0", "1")
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -35,8 +35,8 @@ def binary_and(a: int, b: int) -> str:
if a < 0 or b < 0: if a < 0 or b < 0:
raise ValueError("the value of both inputs must be positive") raise ValueError("the value of both inputs must be positive")
a_binary = str(bin(a))[2:] # remove the leading "0b" a_binary = format(a, "b")
b_binary = str(bin(b))[2:] # remove the leading "0b" b_binary = format(b, "b")
max_len = max(len(a_binary), len(b_binary)) 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. Set the bit at position to 1.
Details: perform bitwise or for given number and X. Details: perform bitwise or for given number and X.
Where X is a number with all the bits zeroes and bit on given Where X is a number with all the bits - zeroes and bit on given
position one. position - one.
>>> set_bit(0b1101, 1) # 0b1111 >>> set_bit(0b1101, 1) # 0b1111
15 15
@ -26,8 +26,8 @@ def clear_bit(number: int, position: int) -> int:
Set the bit at position to 0. Set the bit at position to 0.
Details: perform bitwise and for given number and X. Details: perform bitwise and for given number and X.
Where X is a number with all the bits ones and bit on given Where X is a number with all the bits - ones and bit on given
position zero. position - zero.
>>> clear_bit(0b10010, 1) # 0b10000 >>> clear_bit(0b10010, 1) # 0b10000
16 16
@ -42,8 +42,8 @@ def flip_bit(number: int, position: int) -> int:
Flip the bit at position. Flip the bit at position.
Details: perform bitwise xor for given number and X. Details: perform bitwise xor for given number and X.
Where X is a number with all the bits zeroes and bit on given Where X is a number with all the bits - zeroes and bit on given
position one. position - one.
>>> flip_bit(0b101, 1) # 0b111 >>> flip_bit(0b101, 1) # 0b111
7 7
@ -79,7 +79,7 @@ def get_bit(number: int, position: int) -> int:
Get the bit at the given position Get the bit at the given position
Details: perform bitwise and for the given number and X, 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. If the result is not equal to 0, then the bit on the given position is 1, else 0.
>>> get_bit(0b1010, 0) >>> get_bit(0b1010, 0)

View File

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

View File

@ -24,6 +24,14 @@ def encrypt(plaintext: str, key: str) -> str:
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: plaintext is empty 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): if not isinstance(plaintext, str):
raise TypeError("plaintext must be a string") raise TypeError("plaintext must be a string")
@ -80,6 +88,14 @@ def decrypt(ciphertext: str, key: str) -> str:
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: ciphertext must be a string 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): if not isinstance(ciphertext, str):
raise TypeError("ciphertext must be a string") 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 # Add the margin of error to the total chi squared statistic
chi_squared_statistic += chi_letter_value chi_squared_statistic += chi_letter_value
else: elif letter.lower() in frequencies:
if letter.lower() in frequencies: # Get the amount of times the letter occurs in the message
# Get the amount of times the letter occurs in the message occurrences = decrypted_with_shift.count(letter)
occurrences = decrypted_with_shift.count(letter)
# Get the excepcted amount of times the letter should appear based # Get the excepcted amount of times the letter should appear based
# on letter frequencies # on letter frequencies
expected = frequencies[letter] * occurrences expected = frequencies[letter] * occurrences
# Complete the chi squared statistic formula # Complete the chi squared statistic formula
chi_letter_value = ((occurrences - expected) ** 2) / expected chi_letter_value = ((occurrences - expected) ** 2) / expected
# Add the margin of error to the total chi squared statistic # Add the margin of error to the total chi squared statistic
chi_squared_statistic += chi_letter_value chi_squared_statistic += chi_letter_value
# Add the data to the chi_squared_statistic_values dictionary # Add the data to the chi_squared_statistic_values dictionary
chi_squared_statistic_values[shift] = ( 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 string
import numpy import numpy as np
from maths.greatest_common_divisor import greatest_common_divisor from maths.greatest_common_divisor import greatest_common_divisor
@ -49,11 +49,11 @@ class HillCipher:
# i.e. a total of 36 characters # i.e. a total of 36 characters
# take x and return x % len(key_string) # 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 encrypt_key is an NxN numpy array
""" """
@ -63,7 +63,7 @@ class HillCipher:
def replace_letters(self, letter: str) -> int: 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') >>> hill_cipher.replace_letters('T')
19 19
>>> hill_cipher.replace_letters('0') >>> hill_cipher.replace_letters('0')
@ -73,7 +73,7 @@ class HillCipher:
def replace_digits(self, num: int) -> str: 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) >>> hill_cipher.replace_digits(19)
'T' 'T'
>>> hill_cipher.replace_digits(26) >>> hill_cipher.replace_digits(26)
@ -83,10 +83,10 @@ class HillCipher:
def check_determinant(self) -> None: 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() >>> hill_cipher.check_determinant()
""" """
det = round(numpy.linalg.det(self.encrypt_key)) det = round(np.linalg.det(self.encrypt_key))
if det < 0: if det < 0:
det = det % len(self.key_string) det = det % len(self.key_string)
@ -101,7 +101,7 @@ class HillCipher:
def process_text(self, text: str) -> str: 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') >>> hill_cipher.process_text('Testing Hill Cipher')
'TESTINGHILLCIPHERR' 'TESTINGHILLCIPHERR'
>>> hill_cipher.process_text('hello') >>> hill_cipher.process_text('hello')
@ -117,7 +117,7 @@ class HillCipher:
def encrypt(self, text: str) -> str: 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') >>> hill_cipher.encrypt('testing hill cipher')
'WHXYJOLM9C6XT085LL' 'WHXYJOLM9C6XT085LL'
>>> hill_cipher.encrypt('hello') >>> hill_cipher.encrypt('hello')
@ -129,7 +129,7 @@ class HillCipher:
for i in range(0, len(text) - self.break_key + 1, self.break_key): for i in range(0, len(text) - self.break_key + 1, self.break_key):
batch = text[i : i + self.break_key] batch = text[i : i + self.break_key]
vec = [self.replace_letters(char) for char in batch] 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()[ batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[
0 0
] ]
@ -140,14 +140,14 @@ class HillCipher:
return encrypted 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() >>> hill_cipher.make_decrypt_key()
array([[ 6, 25], array([[ 6, 25],
[ 5, 26]]) [ 5, 26]])
""" """
det = round(numpy.linalg.det(self.encrypt_key)) det = round(np.linalg.det(self.encrypt_key))
if det < 0: if det < 0:
det = det % len(self.key_string) det = det % len(self.key_string)
@ -158,16 +158,14 @@ class HillCipher:
break break
inv_key = ( inv_key = (
det_inv det_inv * np.linalg.det(self.encrypt_key) * np.linalg.inv(self.encrypt_key)
* numpy.linalg.det(self.encrypt_key)
* numpy.linalg.inv(self.encrypt_key)
) )
return self.to_int(self.modulus(inv_key)) return self.to_int(self.modulus(inv_key))
def decrypt(self, text: str) -> str: 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') >>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL')
'TESTINGHILLCIPHERR' 'TESTINGHILLCIPHERR'
>>> hill_cipher.decrypt('85FF00') >>> hill_cipher.decrypt('85FF00')
@ -180,7 +178,7 @@ class HillCipher:
for i in range(0, len(text) - self.break_key + 1, self.break_key): for i in range(0, len(text) - self.break_key + 1, self.break_key):
batch = text[i : i + self.break_key] batch = text[i : i + self.break_key]
vec = [self.replace_letters(char) for char in batch] 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] batch_decrypted = self.modulus(decrypt_key.dot(batch_vec)).T.tolist()[0]
decrypted_batch = "".join( decrypted_batch = "".join(
self.replace_digits(num) for num in batch_decrypted self.replace_digits(num) for num in batch_decrypted
@ -199,7 +197,7 @@ def main() -> None:
row = [int(x) for x in input().split()] row = [int(x) for x in input().split()]
hill_matrix.append(row) 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)") print("Would you like to encrypt or decrypt some text? (1 or 2)")
option = input("\n1. Encrypt\n2. Decrypt\n") option = input("\n1. Encrypt\n2. Decrypt\n")

View File

@ -1,7 +1,7 @@
""" """
https://en.wikipedia.org/wiki/Burrows%E2%80%93Wheeler_transform 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 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 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 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 Run through the list of Letters and build the min heap
for the Huffman Tree. for the Huffman Tree.
""" """
response: list[Letter | TreeNode] = letters # type: ignore response: list[Letter | TreeNode] = list(letters)
while len(response) > 1: while len(response) > 1:
left = response.pop(0) left = response.pop(0)
right = 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): if isinstance(root, Letter):
root.bitstring[root.letter] = bitstring root.bitstring[root.letter] = bitstring
return [root] return [root]
treenode: TreeNode = root # type: ignore treenode: TreeNode = root
letters = [] letters = []
letters += traverse_tree(treenode.left, bitstring + "0") letters += traverse_tree(treenode.left, bitstring + "0")
letters += traverse_tree(treenode.right, bitstring + "1") 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 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: 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 and returns the result as a string
""" """
lexicon = {"0": "0", "1": "1"} 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 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: 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 and returns the result as a string
""" """
lexicon = {"0": "0", "1": "1"} lexicon = {"0": "0", "1": "1"}

View File

@ -25,7 +25,7 @@ import numpy as np
# Importing the Keras libraries and packages # Importing the Keras libraries and packages
import tensorflow as tf import tensorflow as tf
from tensorflow.keras import layers, models from keras import layers, models
if __name__ == "__main__": if __name__ == "__main__":
# Initialising the CNN # 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])) >>> root_mean_square_error(np.array([1, 2, 3]), np.array([6, 4, 2]))
3.1622776601683795 3.1622776601683795
""" """
return np.sqrt(((original - reference) ** 2).mean()) return float(np.sqrt(((original - reference) ** 2).mean()))
def normalize_image( def normalize_image(
@ -141,7 +141,7 @@ def transform(
center_x, center_y = (x // 2 for x in kernel.shape) 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 # to not go out of bounds of the original the image
transformed = np.zeros(image.shape, dtype=np.uint8) transformed = np.zeros(image.shape, dtype=np.uint8)
padded = np.pad(image, 1, "constant", constant_values=constant) 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) >>> morphological = opening_filter(binary)
>>> mask_1 = binary_mask(gray, morphological)[0] >>> mask_1 = binary_mask(gray, morphological)[0]
>>> concurrency = matrix_concurrency(mask_1, (0, 1)) >>> 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] [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, # Function np.indices could be used for bigger input types,
@ -335,7 +335,7 @@ def get_descriptors(
return np.concatenate(descriptors, axis=None) 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, Simple method for calculating the euclidean distance between two points,
with type np.ndarray. with type np.ndarray.
@ -346,7 +346,7 @@ def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32:
>>> euclidean(a, b) >>> euclidean(a, b)
3.3166247903554 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]]: 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 1.660540199e-23
>>> weight_conversion("atomic-mass-unit","atomic-mass-unit",2) >>> weight_conversion("atomic-mass-unit","atomic-mass-unit",2)
1.999999998903455 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: if to_type not in KILOGRAM_CHART or from_type not in WEIGHT_TYPE_CHART:
msg = ( 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]] dplaces = [s for s in u if d in values[s]]
if len(dplaces) == 0: if len(dplaces) == 0:
return False ## Contradiction: no place for this value return False ## Contradiction: no place for this value
elif len(dplaces) == 1: # d can only be in one place in unit; assign it there
# d can only be in one place in unit; assign it there elif len(dplaces) == 1 and not assign(values, dplaces[0], d):
if not assign(values, dplaces[0], d): return False
return False
return values return values
@ -151,7 +150,7 @@ def solve_all(grids, name="", showif=0.0):
display(grid_values(grid)) display(grid_values(grid))
if values: if values:
display(values) display(values)
print("(%.5f seconds)\n" % t) print(f"({t:.5f} seconds)\n")
return (t, solved(values)) return (t, solved(values))
times, results = zip(*[time_solve(grid) for grid in grids]) times, results = zip(*[time_solve(grid) for grid in grids])
@ -173,7 +172,7 @@ def solved(values):
def from_file(filename, sep="\n"): def from_file(filename, sep="\n"):
"Parse a file into a list of strings, separated by sep." "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): def random_puzzle(assignments=17):
@ -218,4 +217,4 @@ if __name__ == "__main__":
start = time.monotonic() start = time.monotonic()
solve(puzzle) solve(puzzle)
t = time.monotonic() - start 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 return root
else: else:
root.set_left(del_node(left_child, data)) root.set_left(del_node(left_child, data))
else: # root.get_data() < data # root.get_data() < data
if right_child is None: elif right_child is None:
return root return root
else: else:
root.set_right(del_node(right_child, data)) root.set_right(del_node(right_child, data))
if get_height(right_child) - get_height(left_child) == 2: if get_height(right_child) - get_height(left_child) == 2:
assert right_child is not None assert right_child is not None

View File

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

View File

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

View File

@ -74,14 +74,13 @@ class BinarySearchTree:
def _put(self, node: Node | None, label: int, parent: Node | None = None) -> Node: def _put(self, node: Node | None, label: int, parent: Node | None = None) -> Node:
if node is None: if node is None:
node = Node(label, parent) 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: else:
if label < node.label: msg = f"Node with label {label} already exists"
node.left = self._put(node.left, label, node) raise ValueError(msg)
elif label > node.label:
node.right = self._put(node.right, label, node)
else:
msg = f"Node with label {label} already exists"
raise ValueError(msg)
return node return node
@ -106,11 +105,10 @@ class BinarySearchTree:
if node is None: if node is None:
msg = f"Node with label {label} does not exist" msg = f"Node with label {label} does not exist"
raise ValueError(msg) raise ValueError(msg)
else: elif label < node.label:
if label < node.label: node = self._search(node.left, label)
node = self._search(node.left, label) elif label > node.label:
elif label > node.label: node = self._search(node.right, label)
node = self._search(node.right, label)
return node 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. 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. 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: 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: Returns a list of nodes value from a particular level:
Left to right direction of the binary tree. 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]: 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: Returns a list of nodes value from a particular level:
Right to left direction of the binary tree. 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]: def populate_output(root: Node | None, level: int) -> Generator[int, None, None]:
if root is None: if not root:
return return
if level == 1: if level == 1:
yield root.data yield root.data
@ -158,6 +168,8 @@ def zigzag(root: Node | None) -> Generator[int, None, None]:
""" """
ZigZag traverse: ZigZag traverse:
Returns a list of nodes value from left to right and right to left, alternatively. 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: if root is None:
return return

View File

@ -80,9 +80,9 @@ class Node:
""" """
if self.left and (self.data < self.left.data or not self.left.is_sorted): if self.left and (self.data < self.left.data or not self.left.is_sorted):
return False return False
if self.right and (self.data > self.right.data or not self.right.is_sorted): return not (
return False self.right and (self.data > self.right.data or not self.right.is_sorted)
return True )
if __name__ == "__main__": 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 p = p // 2
self.st[p] = self.fn(self.st[p * 2], self.st[p * 2 + 1]) 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 Get range query value in log(N) time
:param l: left element index :param left: left element index
:param r: right element index :param right: right element index
:return: element combined in the range [l, r] :return: element combined in the range [left, right]
>>> st = SegmentTree([1, 2, 3, 4], lambda a, b: a + b) >>> st = SegmentTree([1, 2, 3, 4], lambda a, b: a + b)
>>> st.query(0, 2) >>> st.query(0, 2)
@ -104,15 +104,15 @@ class SegmentTree(Generic[T]):
>>> st.query(2, 3) >>> st.query(2, 3)
7 7
""" """
l, r = l + self.N, r + self.N left, right = left + self.N, right + self.N
res: T | None = None res: T | None = None
while l <= r: while left <= right:
if l % 2 == 1: if left % 2 == 1:
res = self.st[l] if res is None else self.fn(res, self.st[l]) res = self.st[left] if res is None else self.fn(res, self.st[left])
if r % 2 == 0: if right % 2 == 0:
res = self.st[r] if res is None else self.fn(res, self.st[r]) res = self.st[right] if res is None else self.fn(res, self.st[right])
l, r = (l + 1) // 2, (r - 1) // 2 left, right = (left + 1) // 2, (right - 1) // 2
return res return res

View File

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

View File

@ -1,8 +1,3 @@
"""
psf/black : true
ruff : passed
"""
from __future__ import annotations from __future__ import annotations
from collections.abc import Iterator from collections.abc import Iterator
@ -17,7 +12,7 @@ class RedBlackTree:
and slower for reading in the average case, though, because they're and slower for reading in the average case, though, because they're
both balanced binary search trees, both will get the same asymptotic both balanced binary search trees, both will get the same asymptotic
performance. 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 Unless otherwise specified, all asymptotic runtimes are specified in
terms of the size of the tree. terms of the size of the tree.
""" """
@ -107,12 +102,11 @@ class RedBlackTree:
else: else:
self.left = RedBlackTree(label, 1, self) self.left = RedBlackTree(label, 1, self)
self.left._insert_repair() self.left._insert_repair()
elif self.right:
self.right.insert(label)
else: else:
if self.right: self.right = RedBlackTree(label, 1, self)
self.right.insert(label) self.right._insert_repair()
else:
self.right = RedBlackTree(label, 1, self)
self.right._insert_repair()
return self.parent or self return self.parent or self
def _insert_repair(self) -> None: def _insert_repair(self) -> None:
@ -153,7 +147,7 @@ class RedBlackTree:
self.grandparent.color = 1 self.grandparent.color = 1
self.grandparent._insert_repair() self.grandparent._insert_repair()
def remove(self, label: int) -> RedBlackTree: # noqa: PLR0912 def remove(self, label: int) -> RedBlackTree:
"""Remove label from this tree.""" """Remove label from this tree."""
if self.label == label: if self.label == label:
if self.left and self.right: if self.left and self.right:
@ -178,36 +172,34 @@ class RedBlackTree:
self.parent.left = None self.parent.left = None
else: else:
self.parent.right = None self.parent.right = None
else: # The node is black
# The node is black elif child is None:
if child is None: # This node and its child are black
# This node and its child are black if self.parent is None:
if self.parent is None: # The tree is now empty
# The tree is now empty return RedBlackTree(None)
return RedBlackTree(None)
else:
self._remove_repair()
if self.is_left():
self.parent.left = None
else:
self.parent.right = None
self.parent = None
else: else:
# This node is black and its child is red self._remove_repair()
# Move the child node here and make it black if self.is_left():
self.label = child.label self.parent.left = None
self.left = child.left else:
self.right = child.right self.parent.right = None
if self.left: self.parent = None
self.left.parent = self else:
if self.right: # This node is black and its child is red
self.right.parent = self # 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: elif self.label is not None and self.label > label:
if self.left: if self.left:
self.left.remove(label) self.left.remove(label)
else: elif self.right:
if self.right: self.right.remove(label)
self.right.remove(label)
return self.parent or self return self.parent or self
def _remove_repair(self) -> None: def _remove_repair(self) -> None:
@ -324,9 +316,7 @@ class RedBlackTree:
return False return False
if self.left and not self.left.check_coloring(): if self.left and not self.left.check_coloring():
return False return False
if self.right and not self.right.check_coloring(): return not (self.right and not self.right.check_coloring())
return False
return True
def black_height(self) -> int | None: def black_height(self) -> int | None:
"""Returns the number of black nodes from this node to the """Returns the number of black nodes from this node to the
@ -369,11 +359,10 @@ class RedBlackTree:
return None return None
else: else:
return self.right.search(label) return self.right.search(label)
elif self.left is None:
return None
else: else:
if self.left is None: return self.left.search(label)
return None
else:
return self.left.search(label)
def floor(self, label: int) -> int | None: def floor(self, label: int) -> int | None:
"""Returns the largest element in this tree which is at most label. """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 = RedBlackTree(10, parent=right_rot.right)
right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.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) right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right)
if tree != right_rot: return tree == right_rot
return False
return True
def test_insertion_speed() -> bool: def test_insertion_speed() -> bool:
@ -610,13 +597,11 @@ def test_insert_and_search() -> bool:
tree.insert(12) tree.insert(12)
tree.insert(10) tree.insert(10)
tree.insert(11) 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 # Found something not in there
return False return False
if not (11 in tree and 12 in tree and -8 in tree and 0 in tree): # Find all these things in there
# Didn't find something in there return all(i in tree for i in (11, 12, -8, 0))
return False
return True
def test_insert_delete() -> bool: def test_insert_delete() -> bool:
@ -638,9 +623,7 @@ def test_insert_delete() -> bool:
tree = tree.remove(9) tree = tree.remove(9)
if not tree.check_color_properties(): if not tree.check_color_properties():
return False return False
if list(tree.inorder_traverse()) != [-8, 0, 4, 8, 10, 11, 12]: return list(tree.inorder_traverse()) == [-8, 0, 4, 8, 10, 11, 12]
return False
return True
def test_floor_ceil() -> bool: def test_floor_ceil() -> bool:
@ -668,9 +651,7 @@ def test_min_max() -> bool:
tree.insert(24) tree.insert(24)
tree.insert(20) tree.insert(20)
tree.insert(22) tree.insert(22)
if tree.get_max() != 22 or tree.get_min() != -16: return not (tree.get_max() != 22 or tree.get_min() != -16)
return False
return True
def test_tree_traversal() -> bool: def test_tree_traversal() -> bool:
@ -686,9 +667,7 @@ def test_tree_traversal() -> bool:
return False return False
if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]:
return False return False
if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]: return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0]
return False
return True
def test_tree_chaining() -> bool: def test_tree_chaining() -> bool:
@ -699,9 +678,7 @@ def test_tree_chaining() -> bool:
return False return False
if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]:
return False return False
if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]: return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0]
return False
return True
def print_results(msg: str, passes: bool) -> None: def print_results(msg: str, passes: bool) -> None:

View File

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

View File

@ -13,7 +13,21 @@ from dataclasses import dataclass
@dataclass @dataclass
class Node: 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 data: int
@ -24,12 +38,25 @@ class Node:
def make_symmetric_tree() -> Node: def make_symmetric_tree() -> Node:
r""" r"""
Create a symmetric tree for testing. Create a symmetric tree for testing.
The tree looks like this: The tree looks like this:
1 1
/ \ / \
2 2 2 2
/ \ / \ / \ / \
3 4 4 3 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 = Node(1)
root.left = Node(2) root.left = Node(2)
@ -43,13 +70,26 @@ def make_symmetric_tree() -> Node:
def make_asymmetric_tree() -> Node: def make_asymmetric_tree() -> Node:
r""" r"""
Create a asymmetric tree for testing. Create an asymmetric tree for testing.
The tree looks like this: The tree looks like this:
1 1
/ \ / \
2 2 2 2
/ \ / \ / \ / \
3 4 3 4 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 = Node(1)
root.left = Node(2) root.left = Node(2)
@ -63,7 +103,15 @@ def make_asymmetric_tree() -> Node:
def is_symmetric_tree(tree: Node) -> bool: 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()) >>> is_symmetric_tree(make_symmetric_tree())
True True
>>> is_symmetric_tree(make_asymmetric_tree()) >>> 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: 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 = make_symmetric_tree()
>>> tree1.right.right = Node(3)
>>> is_mirror(tree1.left, tree1.right) >>> is_mirror(tree1.left, tree1.right)
True True
>>> tree2 = make_asymmetric_tree() >>> 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. # One side is empty while the other is not, which is not symmetric.
return False return False
if left.data == right.data: 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 is_mirror(left.left, right.right) and is_mirror(left.right, right.left)
return False 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. Left tree contains all values less than split value.
Right tree contains all values greater or equal, 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 if root is None or root.value is None: # None tree is split into 2 Nones
return None, None
elif root.value is None:
return None, None 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: else:
if value < root.value: """
""" Just symmetric to previous case
Right tree's root will be current node. """
Now we split(with the same value) current node's left son root.right, right = split(root.right, value)
Left tree: left part of that split return root, right
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
def merge(left: Node | None, right: Node | None) -> Node | None: def merge(left: Node | None, right: Node | None) -> Node | None:

View File

@ -1,4 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from abc import abstractmethod
from .number_theory.prime_numbers import next_prime from .number_theory.prime_numbers import next_prime
@ -173,6 +175,7 @@ class HashTable:
self.values[key] = data self.values[key] = data
self._keys[key] = data self._keys[key] = data
@abstractmethod
def _collision_resolution(self, key, data=None): def _collision_resolution(self, key, data=None):
""" """
This method is a type of open addressing which is used for handling collision. 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): def __init__(self, *args, **kwargs):
super().__init__(*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 Quadratic probing is an open addressing scheme used for resolving
collisions in hash table. collisions in hash table.

View File

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

View File

@ -38,13 +38,12 @@ class BinaryHeap:
def __swap_down(self, i: int) -> None: def __swap_down(self, i: int) -> None:
"""Swap the element down""" """Swap the element down"""
while self.__size >= 2 * i: 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 bigger_child = 2 * i
else: else:
if self.__heap[2 * i] > self.__heap[2 * i + 1]: bigger_child = 2 * i + 1
bigger_child = 2 * i
else:
bigger_child = 2 * i + 1
temporary = self.__heap[i] temporary = self.__heap[i]
if self.__heap[i] < self.__heap[bigger_child]: if self.__heap[i] < self.__heap[bigger_child]:
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 # this is min-heapify method
def sift_down(self, idx, array): def sift_down(self, idx, array):
while True: while True:
l = self.get_left_child_idx(idx) # noqa: E741 left = self.get_left_child_idx(idx)
r = self.get_right_child_idx(idx) right = self.get_right_child_idx(idx)
smallest = idx smallest = idx
if l < len(array) and array[l] < array[idx]: if left < len(array) and array[left] < array[idx]:
smallest = l smallest = left
if r < len(array) and array[r] < array[smallest]: if right < len(array) and array[right] < array[smallest]:
smallest = r smallest = right
if smallest != idx: if smallest != idx:
array[idx], array[smallest] = array[smallest], array[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): def __iter__(self):
node = self node = self
visited = [] visited = set()
while node: while node:
if node in visited: if node in visited:
raise ContainsLoopError raise ContainsLoopError
visited.append(node) visited.add(node)
yield node.data yield node.data
node = node.next_node node = node.next_node

View File

@ -63,7 +63,7 @@ def insert_node(head: Node | None, data: int) -> Node:
while temp_node.next_node: while temp_node.next_node:
temp_node = 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 return head

View File

@ -5,6 +5,7 @@ https://epaperpress.com/sortsearch/download/skiplist.pdf
from __future__ import annotations from __future__ import annotations
from itertools import pairwise
from random import random from random import random
from typing import Generic, TypeVar from typing import Generic, TypeVar
@ -389,7 +390,7 @@ def test_delete_doesnt_leave_dead_nodes():
def test_iter_always_yields_sorted_values(): def test_iter_always_yields_sorted_values():
def is_sorted(lst): 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() skip_list = SkipList()
for i in range(10): for i in range(10):

View File

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

View File

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

View File

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

View File

@ -95,13 +95,12 @@ def infix_2_postfix(infix: str) -> str:
while stack[-1] != "(": while stack[-1] != "(":
post_fix.append(stack.pop()) # Pop stack & add the content to Postfix post_fix.append(stack.pop()) # Pop stack & add the content to Postfix
stack.pop() stack.pop()
else: elif len(stack) == 0:
if len(stack) == 0: stack.append(x) # If stack is empty, push x to stack
stack.append(x) # If stack is empty, push x to stack else: # while priority of x is not > priority of element in the 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]]:
while stack and stack[-1] != "(" and priority[x] <= priority[stack[-1]]: post_fix.append(stack.pop()) # pop stack & add to Postfix
post_fix.append(stack.pop()) # pop stack & add to Postfix stack.append(x) # push x to stack
stack.append(x) # push x to stack
print( print(
x.center(8), 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]: def next_greatest_element_slow(arr: list[float]) -> list[float]:
""" """
Get the Next Greatest Element (NGE) for all elements in a list. Get the Next Greatest Element (NGE) for each element in the array
Maximum element present after the current one which is also greater than the by checking all subsequent elements to find the next greater one.
current 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 >>> next_greatest_element_slow(arr) == expect
True 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]: def next_greatest_element_fast(arr: list[float]) -> list[float]:
""" """
Like next_greatest_element_slow() but changes the loops to use Find the Next Greatest Element (NGE) for each element in the array
enumerate() instead of range(len()) for the outer loop and using a more readable approach. This implementation utilizes
for in a slice of arr for the inner loop. 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 >>> next_greatest_element_fast(arr) == expect
True True
""" """
@ -47,14 +70,23 @@ def next_greatest_element_fast(arr: list[float]) -> list[float]:
def next_greatest_element(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. Efficient solution to find the Next Greatest Element (NGE) for all elements
Maximum element present after the current one which is also greater than the using a stack. The time complexity is reduced to O(n), making it suitable
current one. for larger arrays.
A naive way to solve this is to take two loops and check for the next bigger The stack keeps track of elements for which the next greater element hasn't
number but that will make the time complexity as O(n^2). The better way to solve been found yet. By iterating through the array in reverse (from the last
this would be to use a stack to keep track of maximum number giving a linear time element to the first), the stack is used to efficiently determine the next
solution. 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 >>> next_greatest_element(arr) == expect
True 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 # We have word remaining so we check the next node
elif remaining_word != "": elif remaining_word != "":
return incoming_node.delete(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: else:
# If it is not a leaf, we don't have to delete # We delete the nodes if no edges go from it
if not incoming_node.is_leaf: if len(incoming_node.nodes) == 0:
return False 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: else:
# We delete the nodes if no edges go from it merging_node = next(iter(incoming_node.nodes.values()))
if len(incoming_node.nodes) == 0: incoming_node.is_leaf = merging_node.is_leaf
del self.nodes[word[0]] incoming_node.prefix += merging_node.prefix
# We merge the current node with its only child incoming_node.nodes = merging_node.nodes
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
return True return True
def print_tree(self, height: int = 0) -> None: def print_tree(self, height: int = 0) -> None:
"""Print the tree """Print the tree

View File

@ -74,9 +74,9 @@ def detect_high_low_threshold(
image_shape, destination, threshold_low, threshold_high, weak, strong 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 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 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 an edge pixel's value is smaller than the low threshold value, it will be
suppressed. suppressed.

View File

@ -182,7 +182,7 @@ class IndexCalculation:
Atmospherically Resistant Vegetation Index 2 Atmospherically Resistant Vegetation Index 2
https://www.indexdatabase.de/db/i-single.php?id=396 https://www.indexdatabase.de/db/i-single.php?id=396
:return: index :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))) 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 i in range(points_counts - 1):
for j in range(i + 1, points_counts): for j in range(i + 1, points_counts):
current_dis = euclidean_distance_sqr(points[i], points[j]) current_dis = euclidean_distance_sqr(points[i], points[j])
if current_dis < min_dis: min_dis = min(min_dis, current_dis)
min_dis = current_dis
return min_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 i in range(min(6, points_counts - 1), points_counts):
for j in range(max(0, i - 6), i): for j in range(max(0, i - 6), i):
current_dis = euclidean_distance_sqr(points[i], points[j]) current_dis = euclidean_distance_sqr(points[i], points[j])
if current_dis < min_dis: min_dis = min(min_dis, current_dis)
min_dis = current_dis
return min_dis return min_dis

View File

@ -274,14 +274,13 @@ def convex_hull_bf(points: list[Point]) -> list[Point]:
points_left_of_ij = True points_left_of_ij = True
elif det_k < 0: elif det_k < 0:
points_right_of_ij = True points_right_of_ij = True
else: # point[i], point[j], point[k] all lie on a straight line
# 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
# 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
# right of point[j], then point[i], point[j] cannot be # part of the convex hull of A
# part of the convex hull of A elif points[k] < points[i] or points[k] > points[j]:
if points[k] < points[i] or points[k] > points[j]: ij_part_of_convex_hull = False
ij_part_of_convex_hull = False break
break
if points_left_of_ij and points_right_of_ij: if points_left_of_ij and points_right_of_ij:
ij_part_of_convex_hull = False 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. Function using divide and conquer to calculate a^b.
It only works for integer 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: if b == 0:
return 1 return 1
@ -13,6 +27,10 @@ def actual_power(a: int, b: int):
def power(a: int, b: int) -> float: 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) >>> power(4,6)
4096 4096
>>> power(2,3) >>> 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 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 of chosen elements is tar. For every element, we have two choices
1. Include the element in our set of chosen elements. 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)) if n == 0: # (F(0), F(1))
return (0, 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 # F(2n+1) = F(n+1)^2+F(n)^2
a, b = _fib(n // 2) a, b = _fib(n // 2)
c = a * (b * 2 - a) c = a * (b * 2 - a)

View File

@ -12,19 +12,58 @@ class Graph:
] # dp[i][j] stores minimum distance from i to j ] # dp[i][j] stores minimum distance from i to j
def add_edge(self, u, v, w): 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 self.dp[u][v] = w
def floyd_warshall(self): 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 k in range(self.n):
for i in range(self.n): for i in range(self.n):
for j 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]) self.dp[i][j] = min(self.dp[i][j], self.dp[i][k] + self.dp[k][j])
def show_min(self, u, v): 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] return self.dp[u][v]
if __name__ == "__main__": if __name__ == "__main__":
import doctest
doctest.testmod()
# Example usage
graph = Graph(5) graph = Graph(5)
graph.add_edge(0, 2, 9) graph.add_edge(0, 2, 9)
graph.add_edge(0, 4, 10) graph.add_edge(0, 4, 10)
@ -38,5 +77,9 @@ if __name__ == "__main__":
graph.add_edge(4, 2, 4) graph.add_edge(4, 2, 4)
graph.add_edge(4, 3, 9) graph.add_edge(4, 3, 9)
graph.floyd_warshall() graph.floyd_warshall()
graph.show_min(1, 4) print(
graph.show_min(0, 3) 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') (2, 'ph')
>>> longest_common_subsequence("computer", "food") >>> longest_common_subsequence("computer", "food")
(1, 'o') (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 # find the length of strings
@ -38,30 +56,30 @@ def longest_common_subsequence(x: str, y: str):
n = len(y) n = len(y)
# declaring the array for storing the dp values # 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 i in range(1, m + 1):
for j in range(1, n + 1): for j in range(1, n + 1):
match = 1 if x[i - 1] == y[j - 1] else 0 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 = "" seq = ""
i, j = m, n i, j = m, n
while i > 0 and j > 0: while i > 0 and j > 0:
match = 1 if x[i - 1] == y[j - 1] else 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: if match == 1:
seq = x[i - 1] + seq seq = x[i - 1] + seq
i -= 1 i -= 1
j -= 1 j -= 1
elif l[i][j] == l[i - 1][j]: elif dp[i][j] == dp[i - 1][j]:
i -= 1 i -= 1
else: else:
j -= 1 j -= 1
return l[m][n], seq return dp[m][n], seq
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -7,14 +7,14 @@
from __future__ import annotations from __future__ import annotations
def ceil_index(v, l, r, key): # noqa: E741 def ceil_index(v, left, right, key):
while r - l > 1: while right - left > 1:
m = (l + r) // 2 middle = (left + right) // 2
if v[m] >= key: if v[middle] >= key:
r = m right = middle
else: else:
l = m # noqa: E741 left = middle
return r return right
def longest_increasing_subsequence_length(v: list[int]) -> int: 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 i in range(1, r + 1):
for j in range(i, 0, -1): for j in range(i, 0, -1):
for prev_combination in dp[j - 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: try:
return sorted(dp[n]) return sorted(dp[n])

0
electronics/__init__.py Normal file
View File

View File

@ -37,10 +37,9 @@ class CircularConvolution:
using matrix method using matrix method
Usage: Usage:
>>> import circular_convolution as cc >>> convolution = CircularConvolution()
>>> convolution = cc.CircularConvolution()
>>> convolution.circular_convolution() >>> 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.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] >>> 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.first_signal = [1, -1, 2, 3, -1]
>>> convolution.second_signal = [1, 2, 3] >>> convolution.second_signal = [1, 2, 3]
>>> convolution.circular_convolution() >>> 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)) final_signal = np.matmul(np.transpose(matrix), np.transpose(self.first_signal))
# rounding-off to two decimal places # 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__": if __name__ == "__main__":

View File

@ -20,8 +20,8 @@ def couloumbs_law(
Reference Reference
---------- ----------
Coulomb (1785) "Premier mémoire sur lélectricité et le magnétisme," Coulomb (1785) "Premier mémoire sur l'électricité et le magnétisme,"
Histoire de lAcadémie Royale des Sciences, pp. 569577. Histoire de l'Académie Royale des Sciences, pp. 569-577.
Parameters 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/) * 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/) * 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 collections.abc import Callable
from typing import Any from typing import Any
import numpy import matplotlib.pyplot as plt
from matplotlib import pyplot import numpy as np
c_cauliflower = 0.25 + 0.0j c_cauliflower = 0.25 + 0.0j
c_polynomial_1 = -0.4 + 0.6j c_polynomial_1 = -0.4 + 0.6j
@ -37,22 +37,20 @@ window_size = 2.0
nb_pixels = 666 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$. Evaluate $e^z + c$.
>>> eval_exponential(0, 0) >>> float(eval_exponential(0, 0))
1.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 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 True
""" """
return numpy.exp(z_values) + c_parameter return np.exp(z_values) + c_parameter
def eval_quadratic_polynomial( def eval_quadratic_polynomial(c_parameter: complex, z_values: np.ndarray) -> np.ndarray:
c_parameter: complex, z_values: numpy.ndarray
) -> numpy.ndarray:
""" """
>>> eval_quadratic_polynomial(0, 2) >>> eval_quadratic_polynomial(0, 2)
4 4
@ -66,7 +64,7 @@ def eval_quadratic_polynomial(
return z_values * z_values + c_parameter 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 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). 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], [ 0.-1.j, 0.+0.j, 0.+1.j],
[ 1.-1.j, 1.+0.j, 1.+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)) 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)) y = y.reshape((1, nb_pixels))
return x + 1.0j * y return x + 1.0j * y
def iterate_function( def iterate_function(
eval_function: Callable[[Any, numpy.ndarray], numpy.ndarray], eval_function: Callable[[Any, np.ndarray], np.ndarray],
function_params: Any, function_params: Any,
nb_iterations: int, nb_iterations: int,
z_0: numpy.ndarray, z_0: np.ndarray,
infinity: float | None = None, infinity: float | None = None,
) -> numpy.ndarray: ) -> np.ndarray:
""" """
Iterate the function "eval_function" exactly nb_iterations times. Iterate the function "eval_function" exactly nb_iterations times.
The first argument of the function is a parameter which is contained in The first argument of the function is a parameter which is contained in
@ -98,22 +96,22 @@ def iterate_function(
values to iterate from. values to iterate from.
This function returns the final iterates. 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,) (3,)
>>> numpy.round(iterate_function(eval_quadratic_polynomial, >>> complex(np.round(iterate_function(eval_quadratic_polynomial,
... 0, ... 0,
... 3, ... 3,
... numpy.array([0,1,2]))[0]) ... np.array([0,1,2]))[0]))
0j 0j
>>> numpy.round(iterate_function(eval_quadratic_polynomial, >>> complex(np.round(iterate_function(eval_quadratic_polynomial,
... 0, ... 0,
... 3, ... 3,
... numpy.array([0,1,2]))[1]) ... np.array([0,1,2]))[1]))
(1+0j) (1+0j)
>>> numpy.round(iterate_function(eval_quadratic_polynomial, >>> complex(np.round(iterate_function(eval_quadratic_polynomial,
... 0, ... 0,
... 3, ... 3,
... numpy.array([0,1,2]))[2]) ... np.array([0,1,2]))[2]))
(256+0j) (256+0j)
""" """
@ -121,8 +119,8 @@ def iterate_function(
for _ in range(nb_iterations): for _ in range(nb_iterations):
z_n = eval_function(function_params, z_n) z_n = eval_function(function_params, z_n)
if infinity is not None: if infinity is not None:
numpy.nan_to_num(z_n, copy=False, nan=infinity) np.nan_to_num(z_n, copy=False, nan=infinity)
z_n[abs(z_n) == numpy.inf] = infinity z_n[abs(z_n) == np.inf] = infinity
return z_n return z_n
@ -130,21 +128,21 @@ def show_results(
function_label: str, function_label: str,
function_params: Any, function_params: Any,
escape_radius: float, escape_radius: float,
z_final: numpy.ndarray, z_final: np.ndarray,
) -> None: ) -> None:
""" """
Plots of whether the absolute value of z_final is greater than 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 value of escape_radius. Adds the function_label and function_params to
the title. 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)).transpose()
abs_z_final[:, :] = abs_z_final[::-1, :] abs_z_final[:, :] = abs_z_final[::-1, :]
pyplot.matshow(abs_z_final < escape_radius) plt.matshow(abs_z_final < escape_radius)
pyplot.title(f"Julia set of ${function_label}$, $c={function_params}$") plt.title(f"Julia set of ${function_label}$, $c={function_params}$")
pyplot.show() plt.show()
def ignore_overflow_warnings() -> None: def ignore_overflow_warnings() -> None:

View File

@ -22,25 +22,25 @@ Requirements (pip):
from __future__ import annotations from __future__ import annotations
import matplotlib.pyplot as plt # type: ignore import matplotlib.pyplot as plt
import numpy import numpy as np
# initial triangle of Koch snowflake # initial triangle of Koch snowflake
VECTOR_1 = numpy.array([0, 0]) VECTOR_1 = np.array([0, 0])
VECTOR_2 = numpy.array([0.5, 0.8660254]) VECTOR_2 = np.array([0.5, 0.8660254])
VECTOR_3 = numpy.array([1, 0]) VECTOR_3 = np.array([1, 0])
INITIAL_VECTORS = [VECTOR_1, VECTOR_2, VECTOR_3, VECTOR_1] INITIAL_VECTORS = [VECTOR_1, VECTOR_2, VECTOR_3, VECTOR_1]
# uncomment for simple Koch curve instead of Koch snowflake # uncomment for simple Koch curve instead of Koch snowflake
# INITIAL_VECTORS = [VECTOR_1, VECTOR_3] # 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". Go through the number of iterations determined by the argument "steps".
Be careful with high values (above 5) since the time to calculate increases Be careful with high values (above 5) since the time to calculate increases
exponentially. 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 , \ [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \
0.28867513]), array([0.66666667, 0. ]), array([1, 0])] 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 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 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 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 the original two vectors. The vector in the middle is constructed through a
60 degree rotation so it is bent outwards. 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 , \ [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \
0.28867513]), array([0.66666667, 0. ]), array([1, 0])] 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 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 Standard rotation of a 2D vector with a rotation matrix
(see https://en.wikipedia.org/wiki/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]) array([0.5 , 0.8660254])
>>> rotate(numpy.array([1, 0]), 90) >>> rotate(np.array([1, 0]), 90)
array([6.123234e-17, 1.000000e+00]) array([6.123234e-17, 1.000000e+00])
""" """
theta = numpy.radians(angle_in_degrees) theta = np.radians(angle_in_degrees)
c, s = numpy.cos(theta), numpy.sin(theta) c, s = np.cos(theta), np.sin(theta)
rotation_matrix = numpy.array(((c, -s), (s, c))) rotation_matrix = np.array(((c, -s), (s, c)))
return numpy.dot(rotation_matrix, vector) 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 Utility function to plot the vectors using matplotlib.pyplot
No doctest was implemented since this function does not have a return value 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