mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-04-27 08:03:39 +00:00
Merge branch 'TheAlgorithms:master' into master
This commit is contained in:
commit
6047fe3098
@ -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 \
|
||||||
|
@ -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
2
.github/CODEOWNERS
vendored
@ -9,8 +9,6 @@
|
|||||||
|
|
||||||
/.* @cclauss
|
/.* @cclauss
|
||||||
|
|
||||||
# /arithmetic_analysis/
|
|
||||||
|
|
||||||
# /backtracking/
|
# /backtracking/
|
||||||
|
|
||||||
# /bit_manipulation/
|
# /bit_manipulation/
|
||||||
|
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@ -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
50
.github/workflows/sphinx.yml
vendored
Normal 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
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
39
DIRECTORY.md
39
DIRECTORY.md
@ -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)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
MIT License
|
## MIT License
|
||||||
|
|
||||||
Copyright (c) 2016-2022 TheAlgorithms and contributors
|
Copyright (c) 2016-2022 TheAlgorithms and contributors
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
71
backtracking/word_break.py
Normal file
71
backtracking/word_break.py
Normal 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
100
backtracking/word_ladder.py
Normal 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)
|
@ -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))
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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] = (
|
||||||
|
45
ciphers/gronsfeld_cipher.py
Normal file
45
ciphers/gronsfeld_cipher.py
Normal 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()
|
@ -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")
|
||||||
|
@ -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 Burrows–Wheeler 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
|
||||||
|
@ -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")
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
One of the several implementations of Lempel–Ziv–Welch 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 Lempel–Ziv–Welch 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"}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
One of the several implementations of Lempel–Ziv–Welch 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 Lempel–Ziv–Welch 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"}
|
||||||
|
@ -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
|
||||||
|
@ -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]]:
|
||||||
|
@ -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 = (
|
||||||
|
0
data_structures/arrays/__init__.py
Normal file
0
data_structures/arrays/__init__.py
Normal 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")
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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__":
|
||||||
|
78
data_structures/binary_tree/maximum_sum_bst.py
Normal file
78
data_structures/binary_tree/maximum_sum_bst.py
Normal 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()
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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/Red–black_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:
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
0
data_structures/hashing/tests/__init__.py
Normal file
0
data_structures/hashing/tests/__init__.py
Normal 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):
|
||||||
"""
|
"""
|
||||||
|
@ -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]
|
||||||
|
@ -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]
|
||||||
|
0
data_structures/kd_tree/__init__.py
Normal file
0
data_structures/kd_tree/__init__.py
Normal file
43
data_structures/kd_tree/build_kdtree.py
Normal file
43
data_structures/kd_tree/build_kdtree.py
Normal 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),
|
||||||
|
)
|
0
data_structures/kd_tree/example/__init__.py
Normal file
0
data_structures/kd_tree/example/__init__.py
Normal file
46
data_structures/kd_tree/example/example_usage.py
Normal file
46
data_structures/kd_tree/example/example_usage.py
Normal 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()
|
29
data_structures/kd_tree/example/hypercube_points.py
Normal file
29
data_structures/kd_tree/example/hypercube_points.py
Normal 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)
|
38
data_structures/kd_tree/kd_node.py
Normal file
38
data_structures/kd_tree/kd_node.py
Normal 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
|
79
data_structures/kd_tree/nearest_neighbour_search.py
Normal file
79
data_structures/kd_tree/nearest_neighbour_search.py
Normal 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
|
0
data_structures/kd_tree/tests/__init__.py
Normal file
0
data_structures/kd_tree/tests/__init__.py
Normal file
108
data_structures/kd_tree/tests/test_kdtree.py
Normal file
108
data_structures/kd_tree/tests/test_kdtree.py
Normal 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()
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
38
data_structures/stacks/lexicographical_numbers.py
Normal file
38
data_structures/stacks/lexicographical_numbers.py
Normal 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))}")
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
0
data_structures/suffix_tree/__init__.py
Normal file
0
data_structures/suffix_tree/__init__.py
Normal file
0
data_structures/suffix_tree/example/__init__.py
Normal file
0
data_structures/suffix_tree/example/__init__.py
Normal file
37
data_structures/suffix_tree/example/example_usage.py
Normal file
37
data_structures/suffix_tree/example/example_usage.py
Normal 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()
|
66
data_structures/suffix_tree/suffix_tree.py
Normal file
66
data_structures/suffix_tree/suffix_tree.py
Normal 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
|
36
data_structures/suffix_tree/suffix_tree_node.py
Normal file
36
data_structures/suffix_tree/suffix_tree_node.py
Normal 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
|
0
data_structures/suffix_tree/tests/__init__.py
Normal file
0
data_structures/suffix_tree/tests/__init__.py
Normal file
59
data_structures/suffix_tree/tests/test_suffix_tree.py
Normal file
59
data_structures/suffix_tree/tests/test_suffix_tree.py
Normal 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()
|
@ -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
|
||||||
|
@ -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 pixel’s 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 pixel’s 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.
|
||||||
|
@ -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.nir−self.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)))
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
0
docs/__init__.py
Normal file
3
docs/conf.py
Normal file
3
docs/conf.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from sphinx_pyproject import SphinxConfig
|
||||||
|
|
||||||
|
project = SphinxConfig("../pyproject.toml", globalns=globals()).name
|
@ -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. Don’t include the element in our set of chosen elements.
|
2. Don't include the element in our set of chosen elements.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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__":
|
||||||
|
@ -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:
|
@ -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
0
electronics/__init__.py
Normal 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__":
|
||||||
|
@ -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 l’Académie Royale des Sciences, pp. 569–577.
|
Histoire de l'Académie Royale des Sciences, pp. 569-577.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
@ -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
0
fractals/__init__.py
Normal 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:
|
||||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user