mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-02-21 16:52:05 +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
|
||||
ARG VARIANT=3.12-bookworm
|
||||
ARG VARIANT=3.13-bookworm
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
|
||||
COPY requirements.txt /tmp/pip-tmp/
|
||||
RUN python3 -m pip install --upgrade pip \
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// Update 'VARIANT' to pick a Python version: 3, 3.11, 3.10, 3.9, 3.8
|
||||
// Append -bullseye or -buster to pin to an OS version.
|
||||
// Use -bullseye variants on local on arm64/Apple Silicon.
|
||||
"VARIANT": "3.12-bookworm",
|
||||
"VARIANT": "3.13-bookworm",
|
||||
}
|
||||
},
|
||||
|
||||
|
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -9,8 +9,6 @@
|
|||
|
||||
/.* @cclauss
|
||||
|
||||
# /arithmetic_analysis/
|
||||
|
||||
# /backtracking/
|
||||
|
||||
# /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/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
python-version: 3.13
|
||||
allow-prereleases: true
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
|
@ -20,13 +20,18 @@ jobs:
|
|||
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools six wheel
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
python -m pip install pytest-cov -r requirements.txt
|
||||
- name: Run tests
|
||||
# TODO: #8818 Re-enable quantum tests
|
||||
run: pytest
|
||||
--ignore=quantum/q_fourier_transform.py
|
||||
--ignore=computer_vision/cnn_classification.py
|
||||
--ignore=docs/conf.py
|
||||
--ignore=dynamic_programming/k_means_clustering_tensorflow.py
|
||||
--ignore=machine_learning/lstm/lstm_prediction.py
|
||||
--ignore=neural_network/input_data.py
|
||||
--ignore=project_euler/
|
||||
--ignore=quantum/q_fourier_transform.py
|
||||
--ignore=scripts/validate_solutions.py
|
||||
--cov-report=term-missing:skip-covered
|
||||
--cov=. .
|
||||
|
|
50
.github/workflows/sphinx.yml
vendored
Normal file
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:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-toml
|
||||
|
@ -11,25 +11,25 @@ repos:
|
|||
- id: requirements-txt-fixer
|
||||
|
||||
- repo: https://github.com/MarcoGorelli/auto-walrus
|
||||
rev: v0.2.2
|
||||
rev: 0.3.4
|
||||
hooks:
|
||||
- id: auto-walrus
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.3.4
|
||||
rev: v0.7.4
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.6
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "1.7.0"
|
||||
rev: "v2.5.0"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
|
@ -42,15 +42,16 @@ repos:
|
|||
pass_filenames: false
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.16
|
||||
rev: v0.23
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.9.0
|
||||
rev: v1.13.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
args:
|
||||
- --explicit-package-bases
|
||||
- --ignore-missing-imports
|
||||
- --install-types # See mirrors-mypy README.md
|
||||
- --non-interactive
|
||||
|
|
|
@ -77,7 +77,7 @@ pre-commit run --all-files --show-diff-on-failure
|
|||
|
||||
We want your work to be readable by others; therefore, we encourage you to note the following:
|
||||
|
||||
- Please write in Python 3.12+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will.
|
||||
- Please write in Python 3.13+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will.
|
||||
- Please focus hard on the naming of functions, classes, and variables. Help your reader by using __descriptive names__ that can help you to remove redundant comments.
|
||||
- Single letter variable names are *old school* so please avoid them unless their life only spans a few lines.
|
||||
- Expand acronyms because `gcd()` is hard to understand but `greatest_common_divisor()` is not.
|
||||
|
@ -96,7 +96,7 @@ We want your work to be readable by others; therefore, we encourage you to note
|
|||
|
||||
```bash
|
||||
python3 -m pip install ruff # only required the first time
|
||||
ruff .
|
||||
ruff check
|
||||
```
|
||||
|
||||
- Original code submission require docstrings or comments to describe your work.
|
||||
|
|
39
DIRECTORY.md
39
DIRECTORY.md
|
@ -22,6 +22,8 @@
|
|||
* [Rat In Maze](backtracking/rat_in_maze.py)
|
||||
* [Sudoku](backtracking/sudoku.py)
|
||||
* [Sum Of Subsets](backtracking/sum_of_subsets.py)
|
||||
* [Word Break](backtracking/word_break.py)
|
||||
* [Word Ladder](backtracking/word_ladder.py)
|
||||
* [Word Search](backtracking/word_search.py)
|
||||
|
||||
## Bit Manipulation
|
||||
|
@ -98,6 +100,7 @@
|
|||
* [Elgamal Key Generator](ciphers/elgamal_key_generator.py)
|
||||
* [Enigma Machine2](ciphers/enigma_machine2.py)
|
||||
* [Fractionated Morse Cipher](ciphers/fractionated_morse_cipher.py)
|
||||
* [Gronsfeld Cipher](ciphers/gronsfeld_cipher.py)
|
||||
* [Hill Cipher](ciphers/hill_cipher.py)
|
||||
* [Mixed Keyword Cypher](ciphers/mixed_keyword_cypher.py)
|
||||
* [Mono Alphabetic Ciphers](ciphers/mono_alphabetic_ciphers.py)
|
||||
|
@ -210,6 +213,7 @@
|
|||
* [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py)
|
||||
* [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py)
|
||||
* [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.py)
|
||||
* [Maximum Sum Bst](data_structures/binary_tree/maximum_sum_bst.py)
|
||||
* [Merge Two Binary Trees](data_structures/binary_tree/merge_two_binary_trees.py)
|
||||
* [Mirror Binary Tree](data_structures/binary_tree/mirror_binary_tree.py)
|
||||
* [Non Recursive Segment Tree](data_structures/binary_tree/non_recursive_segment_tree.py)
|
||||
|
@ -243,6 +247,15 @@
|
|||
* [Min Heap](data_structures/heap/min_heap.py)
|
||||
* [Randomized Heap](data_structures/heap/randomized_heap.py)
|
||||
* [Skew Heap](data_structures/heap/skew_heap.py)
|
||||
* Kd Tree
|
||||
* [Build Kdtree](data_structures/kd_tree/build_kdtree.py)
|
||||
* Example
|
||||
* [Example Usage](data_structures/kd_tree/example/example_usage.py)
|
||||
* [Hypercube Points](data_structures/kd_tree/example/hypercube_points.py)
|
||||
* [Kd Node](data_structures/kd_tree/kd_node.py)
|
||||
* [Nearest Neighbour Search](data_structures/kd_tree/nearest_neighbour_search.py)
|
||||
* Tests
|
||||
* [Test Kdtree](data_structures/kd_tree/tests/test_kdtree.py)
|
||||
* Linked List
|
||||
* [Circular Linked List](data_structures/linked_list/circular_linked_list.py)
|
||||
* [Deque Doubly](data_structures/linked_list/deque_doubly.py)
|
||||
|
@ -274,6 +287,7 @@
|
|||
* [Dijkstras Two Stack Algorithm](data_structures/stacks/dijkstras_two_stack_algorithm.py)
|
||||
* [Infix To Postfix Conversion](data_structures/stacks/infix_to_postfix_conversion.py)
|
||||
* [Infix To Prefix Conversion](data_structures/stacks/infix_to_prefix_conversion.py)
|
||||
* [Lexicographical Numbers](data_structures/stacks/lexicographical_numbers.py)
|
||||
* [Next Greater Element](data_structures/stacks/next_greater_element.py)
|
||||
* [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py)
|
||||
* [Prefix Evaluation](data_structures/stacks/prefix_evaluation.py)
|
||||
|
@ -282,6 +296,13 @@
|
|||
* [Stack With Doubly Linked List](data_structures/stacks/stack_with_doubly_linked_list.py)
|
||||
* [Stack With Singly Linked List](data_structures/stacks/stack_with_singly_linked_list.py)
|
||||
* [Stock Span Problem](data_structures/stacks/stock_span_problem.py)
|
||||
* Suffix Tree
|
||||
* Example
|
||||
* [Example Usage](data_structures/suffix_tree/example/example_usage.py)
|
||||
* [Suffix Tree](data_structures/suffix_tree/suffix_tree.py)
|
||||
* [Suffix Tree Node](data_structures/suffix_tree/suffix_tree_node.py)
|
||||
* Tests
|
||||
* [Test Suffix Tree](data_structures/suffix_tree/tests/test_suffix_tree.py)
|
||||
* Trie
|
||||
* [Radix Tree](data_structures/trie/radix_tree.py)
|
||||
* [Trie](data_structures/trie/trie.py)
|
||||
|
@ -330,6 +351,9 @@
|
|||
* [Power](divide_and_conquer/power.py)
|
||||
* [Strassen Matrix Multiplication](divide_and_conquer/strassen_matrix_multiplication.py)
|
||||
|
||||
## Docs
|
||||
* [Conf](docs/conf.py)
|
||||
|
||||
## Dynamic Programming
|
||||
* [Abbreviation](dynamic_programming/abbreviation.py)
|
||||
* [All Construct](dynamic_programming/all_construct.py)
|
||||
|
@ -351,7 +375,7 @@
|
|||
* [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py)
|
||||
* [Longest Common Substring](dynamic_programming/longest_common_substring.py)
|
||||
* [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py)
|
||||
* [Longest Increasing Subsequence O(Nlogn)](dynamic_programming/longest_increasing_subsequence_o(nlogn).py)
|
||||
* [Longest Increasing Subsequence O Nlogn](dynamic_programming/longest_increasing_subsequence_o_nlogn.py)
|
||||
* [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py)
|
||||
* [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py)
|
||||
* [Matrix Chain Order](dynamic_programming/matrix_chain_order.py)
|
||||
|
@ -465,7 +489,7 @@
|
|||
* [Dijkstra Alternate](graphs/dijkstra_alternate.py)
|
||||
* [Dijkstra Binary Grid](graphs/dijkstra_binary_grid.py)
|
||||
* [Dinic](graphs/dinic.py)
|
||||
* [Directed And Undirected (Weighted) Graph](graphs/directed_and_undirected_(weighted)_graph.py)
|
||||
* [Directed And Undirected Weighted Graph](graphs/directed_and_undirected_weighted_graph.py)
|
||||
* [Edmonds Karp Multiple Source And Sink](graphs/edmonds_karp_multiple_source_and_sink.py)
|
||||
* [Eulerian Path And Circuit For Undirected Graph](graphs/eulerian_path_and_circuit_for_undirected_graph.py)
|
||||
* [Even Tree](graphs/even_tree.py)
|
||||
|
@ -540,8 +564,7 @@
|
|||
* [Lu Decomposition](linear_algebra/lu_decomposition.py)
|
||||
* Src
|
||||
* [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py)
|
||||
* Gaussian Elimination Pivoting
|
||||
* [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py)
|
||||
* [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting.py)
|
||||
* [Lib](linear_algebra/src/lib.py)
|
||||
* [Polynom For Points](linear_algebra/src/polynom_for_points.py)
|
||||
* [Power Iteration](linear_algebra/src/power_iteration.py)
|
||||
|
@ -661,7 +684,6 @@
|
|||
* [Manhattan Distance](maths/manhattan_distance.py)
|
||||
* [Matrix Exponentiation](maths/matrix_exponentiation.py)
|
||||
* [Max Sum Sliding Window](maths/max_sum_sliding_window.py)
|
||||
* [Median Of Two Arrays](maths/median_of_two_arrays.py)
|
||||
* [Minkowski Distance](maths/minkowski_distance.py)
|
||||
* [Mobius Function](maths/mobius_function.py)
|
||||
* [Modular Division](maths/modular_division.py)
|
||||
|
@ -773,6 +795,7 @@
|
|||
* [Inverse Of Matrix](matrix/inverse_of_matrix.py)
|
||||
* [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py)
|
||||
* [Matrix Class](matrix/matrix_class.py)
|
||||
* [Matrix Equalization](matrix/matrix_equalization.py)
|
||||
* [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py)
|
||||
* [Matrix Operation](matrix/matrix_operation.py)
|
||||
* [Max Area Of Island](matrix/max_area_of_island.py)
|
||||
|
@ -792,7 +815,6 @@
|
|||
* [Minimum Cut](networking_flow/minimum_cut.py)
|
||||
|
||||
## Neural Network
|
||||
* [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py)
|
||||
* Activation Functions
|
||||
* [Binary Step](neural_network/activation_functions/binary_step.py)
|
||||
* [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py)
|
||||
|
@ -809,6 +831,7 @@
|
|||
* [Convolution Neural Network](neural_network/convolution_neural_network.py)
|
||||
* [Input Data](neural_network/input_data.py)
|
||||
* [Simple Neural Network](neural_network/simple_neural_network.py)
|
||||
* [Two Hidden Layers Neural Network](neural_network/two_hidden_layers_neural_network.py)
|
||||
|
||||
## Other
|
||||
* [Activity Selection](other/activity_selection.py)
|
||||
|
@ -863,6 +886,7 @@
|
|||
* [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py)
|
||||
* [Photoelectric Effect](physics/photoelectric_effect.py)
|
||||
* [Potential Energy](physics/potential_energy.py)
|
||||
* [Rainfall Intensity](physics/rainfall_intensity.py)
|
||||
* [Reynolds Number](physics/reynolds_number.py)
|
||||
* [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py)
|
||||
* [Shear Stress](physics/shear_stress.py)
|
||||
|
@ -1184,6 +1208,7 @@
|
|||
* [Binary Tree Traversal](searches/binary_tree_traversal.py)
|
||||
* [Double Linear Search](searches/double_linear_search.py)
|
||||
* [Double Linear Search Recursion](searches/double_linear_search_recursion.py)
|
||||
* [Exponential Search](searches/exponential_search.py)
|
||||
* [Fibonacci Search](searches/fibonacci_search.py)
|
||||
* [Hill Climbing](searches/hill_climbing.py)
|
||||
* [Interpolation Search](searches/interpolation_search.py)
|
||||
|
@ -1259,6 +1284,7 @@
|
|||
* [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py)
|
||||
* [Capitalize](strings/capitalize.py)
|
||||
* [Check Anagrams](strings/check_anagrams.py)
|
||||
* [Count Vowels](strings/count_vowels.py)
|
||||
* [Credit Card Validator](strings/credit_card_validator.py)
|
||||
* [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py)
|
||||
* [Detecting English Programmatically](strings/detecting_english_programmatically.py)
|
||||
|
@ -1326,7 +1352,6 @@
|
|||
* [Get Ip Geolocation](web_programming/get_ip_geolocation.py)
|
||||
* [Get Top Billionaires](web_programming/get_top_billionaires.py)
|
||||
* [Get Top Hn Posts](web_programming/get_top_hn_posts.py)
|
||||
* [Get User Tweets](web_programming/get_user_tweets.py)
|
||||
* [Giphy](web_programming/giphy.py)
|
||||
* [Instagram Crawler](web_programming/instagram_crawler.py)
|
||||
* [Instagram Pic](web_programming/instagram_pic.py)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
MIT License
|
||||
## MIT License
|
||||
|
||||
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(
|
||||
frequency: int,
|
||||
samplerate: int,
|
||||
q_factor: float = 1 / sqrt(2), # noqa: B008
|
||||
q_factor: float = 1 / sqrt(2),
|
||||
) -> IIRFilter:
|
||||
"""
|
||||
Creates a low-pass filter
|
||||
|
@ -43,7 +43,7 @@ def make_lowpass(
|
|||
def make_highpass(
|
||||
frequency: int,
|
||||
samplerate: int,
|
||||
q_factor: float = 1 / sqrt(2), # noqa: B008
|
||||
q_factor: float = 1 / sqrt(2),
|
||||
) -> IIRFilter:
|
||||
"""
|
||||
Creates a high-pass filter
|
||||
|
@ -73,7 +73,7 @@ def make_highpass(
|
|||
def make_bandpass(
|
||||
frequency: int,
|
||||
samplerate: int,
|
||||
q_factor: float = 1 / sqrt(2), # noqa: B008
|
||||
q_factor: float = 1 / sqrt(2),
|
||||
) -> IIRFilter:
|
||||
"""
|
||||
Creates a band-pass filter
|
||||
|
@ -104,7 +104,7 @@ def make_bandpass(
|
|||
def make_allpass(
|
||||
frequency: int,
|
||||
samplerate: int,
|
||||
q_factor: float = 1 / sqrt(2), # noqa: B008
|
||||
q_factor: float = 1 / sqrt(2),
|
||||
) -> IIRFilter:
|
||||
"""
|
||||
Creates an all-pass filter
|
||||
|
@ -132,7 +132,7 @@ def make_peak(
|
|||
frequency: int,
|
||||
samplerate: int,
|
||||
gain_db: float,
|
||||
q_factor: float = 1 / sqrt(2), # noqa: B008
|
||||
q_factor: float = 1 / sqrt(2),
|
||||
) -> IIRFilter:
|
||||
"""
|
||||
Creates a peak filter
|
||||
|
@ -164,7 +164,7 @@ def make_lowshelf(
|
|||
frequency: int,
|
||||
samplerate: int,
|
||||
gain_db: float,
|
||||
q_factor: float = 1 / sqrt(2), # noqa: B008
|
||||
q_factor: float = 1 / sqrt(2),
|
||||
) -> IIRFilter:
|
||||
"""
|
||||
Creates a low-shelf filter
|
||||
|
@ -201,7 +201,7 @@ def make_highshelf(
|
|||
frequency: int,
|
||||
samplerate: int,
|
||||
gain_db: float,
|
||||
q_factor: float = 1 / sqrt(2), # noqa: B008
|
||||
q_factor: float = 1 / sqrt(2),
|
||||
) -> IIRFilter:
|
||||
"""
|
||||
Creates a high-shelf filter
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from math import pi
|
||||
from typing import Protocol
|
||||
|
||||
|
@ -8,6 +9,7 @@ import numpy as np
|
|||
|
||||
|
||||
class FilterType(Protocol):
|
||||
@abstractmethod
|
||||
def process(self, sample: float) -> float:
|
||||
"""
|
||||
Calculate y[n]
|
||||
|
@ -15,7 +17,6 @@ class FilterType(Protocol):
|
|||
>>> issubclass(FilterType, Protocol)
|
||||
True
|
||||
"""
|
||||
return 0.0
|
||||
|
||||
|
||||
def get_bounds(
|
||||
|
|
|
@ -23,6 +23,42 @@ def create_state_space_tree(
|
|||
Creates a state space tree to iterate through each branch using DFS.
|
||||
We know that each state has exactly len(sequence) - index children.
|
||||
It terminates when it reaches the end of the given sequence.
|
||||
|
||||
:param sequence: The input sequence for which permutations are generated.
|
||||
:param current_sequence: The current permutation being built.
|
||||
:param index: The current index in the sequence.
|
||||
:param index_used: list to track which elements are used in permutation.
|
||||
|
||||
Example 1:
|
||||
>>> sequence = [1, 2, 3]
|
||||
>>> current_sequence = []
|
||||
>>> index_used = [False, False, False]
|
||||
>>> create_state_space_tree(sequence, current_sequence, 0, index_used)
|
||||
[1, 2, 3]
|
||||
[1, 3, 2]
|
||||
[2, 1, 3]
|
||||
[2, 3, 1]
|
||||
[3, 1, 2]
|
||||
[3, 2, 1]
|
||||
|
||||
Example 2:
|
||||
>>> sequence = ["A", "B", "C"]
|
||||
>>> current_sequence = []
|
||||
>>> index_used = [False, False, False]
|
||||
>>> create_state_space_tree(sequence, current_sequence, 0, index_used)
|
||||
['A', 'B', 'C']
|
||||
['A', 'C', 'B']
|
||||
['B', 'A', 'C']
|
||||
['B', 'C', 'A']
|
||||
['C', 'A', 'B']
|
||||
['C', 'B', 'A']
|
||||
|
||||
Example 3:
|
||||
>>> sequence = [1]
|
||||
>>> current_sequence = []
|
||||
>>> index_used = [False]
|
||||
>>> create_state_space_tree(sequence, current_sequence, 0, index_used)
|
||||
[1]
|
||||
"""
|
||||
|
||||
if index == len(sequence):
|
||||
|
|
|
@ -22,6 +22,56 @@ def create_state_space_tree(
|
|||
Creates a state space tree to iterate through each branch using DFS.
|
||||
We know that each state has exactly two children.
|
||||
It terminates when it reaches the end of the given sequence.
|
||||
|
||||
:param sequence: The input sequence for which subsequences are generated.
|
||||
:param current_subsequence: The current subsequence being built.
|
||||
:param index: The current index in the sequence.
|
||||
|
||||
Example:
|
||||
>>> sequence = [3, 2, 1]
|
||||
>>> current_subsequence = []
|
||||
>>> create_state_space_tree(sequence, current_subsequence, 0)
|
||||
[]
|
||||
[1]
|
||||
[2]
|
||||
[2, 1]
|
||||
[3]
|
||||
[3, 1]
|
||||
[3, 2]
|
||||
[3, 2, 1]
|
||||
|
||||
>>> sequence = ["A", "B"]
|
||||
>>> current_subsequence = []
|
||||
>>> create_state_space_tree(sequence, current_subsequence, 0)
|
||||
[]
|
||||
['B']
|
||||
['A']
|
||||
['A', 'B']
|
||||
|
||||
>>> sequence = []
|
||||
>>> current_subsequence = []
|
||||
>>> create_state_space_tree(sequence, current_subsequence, 0)
|
||||
[]
|
||||
|
||||
>>> sequence = [1, 2, 3, 4]
|
||||
>>> current_subsequence = []
|
||||
>>> create_state_space_tree(sequence, current_subsequence, 0)
|
||||
[]
|
||||
[4]
|
||||
[3]
|
||||
[3, 4]
|
||||
[2]
|
||||
[2, 4]
|
||||
[2, 3]
|
||||
[2, 3, 4]
|
||||
[1]
|
||||
[1, 4]
|
||||
[1, 3]
|
||||
[1, 3, 4]
|
||||
[1, 2]
|
||||
[1, 2, 4]
|
||||
[1, 2, 3]
|
||||
[1, 2, 3, 4]
|
||||
"""
|
||||
|
||||
if index == len(sequence):
|
||||
|
@ -35,7 +85,7 @@ def create_state_space_tree(
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
seq: list[Any] = [3, 1, 2, 4]
|
||||
seq: list[Any] = [1, 2, 3]
|
||||
generate_all_subsequences(seq)
|
||||
|
||||
seq.clear()
|
||||
|
|
|
@ -28,9 +28,8 @@ def is_valid(
|
|||
if vertical:
|
||||
if row + i >= len(puzzle) or puzzle[row + i][col] != "":
|
||||
return False
|
||||
else:
|
||||
if col + i >= len(puzzle[0]) or puzzle[row][col + i] != "":
|
||||
return False
|
||||
elif col + i >= len(puzzle[0]) or puzzle[row][col + i] != "":
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -24,10 +24,10 @@ def get_valid_pos(position: tuple[int, int], n: int) -> list[tuple[int, int]]:
|
|||
]
|
||||
permissible_positions = []
|
||||
|
||||
for position in positions:
|
||||
y_test, x_test = position
|
||||
for inner_position in positions:
|
||||
y_test, x_test = inner_position
|
||||
if 0 <= y_test < n and 0 <= x_test < n:
|
||||
permissible_positions.append(position)
|
||||
permissible_positions.append(inner_position)
|
||||
|
||||
return permissible_positions
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Given a partially filled 9×9 2D array, the objective is to fill a 9×9
|
||||
Given a partially filled 9x9 2D array, the objective is to fill a 9x9
|
||||
square grid with digits numbered 1 to 9, so that every row, column, and
|
||||
and each of the nine 3×3 sub-grids contains all of the digits.
|
||||
and each of the nine 3x3 sub-grids contains all of the digits.
|
||||
|
||||
This can be solved using Backtracking and is similar to n-queens.
|
||||
We check to see if a cell is safe or not and recursively call the
|
||||
|
|
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)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: 'float' object cannot be interpreted as an integer
|
||||
ValueError: Unknown format code 'b' for object of type 'float'
|
||||
>>> binary_and("0", "1")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -35,8 +35,8 @@ def binary_and(a: int, b: int) -> str:
|
|||
if a < 0 or b < 0:
|
||||
raise ValueError("the value of both inputs must be positive")
|
||||
|
||||
a_binary = str(bin(a))[2:] # remove the leading "0b"
|
||||
b_binary = str(bin(b))[2:] # remove the leading "0b"
|
||||
a_binary = format(a, "b")
|
||||
b_binary = format(b, "b")
|
||||
|
||||
max_len = max(len(a_binary), len(b_binary))
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ def set_bit(number: int, position: int) -> int:
|
|||
Set the bit at position to 1.
|
||||
|
||||
Details: perform bitwise or for given number and X.
|
||||
Where X is a number with all the bits – zeroes and bit on given
|
||||
position – one.
|
||||
Where X is a number with all the bits - zeroes and bit on given
|
||||
position - one.
|
||||
|
||||
>>> set_bit(0b1101, 1) # 0b1111
|
||||
15
|
||||
|
@ -26,8 +26,8 @@ def clear_bit(number: int, position: int) -> int:
|
|||
Set the bit at position to 0.
|
||||
|
||||
Details: perform bitwise and for given number and X.
|
||||
Where X is a number with all the bits – ones and bit on given
|
||||
position – zero.
|
||||
Where X is a number with all the bits - ones and bit on given
|
||||
position - zero.
|
||||
|
||||
>>> clear_bit(0b10010, 1) # 0b10000
|
||||
16
|
||||
|
@ -42,8 +42,8 @@ def flip_bit(number: int, position: int) -> int:
|
|||
Flip the bit at position.
|
||||
|
||||
Details: perform bitwise xor for given number and X.
|
||||
Where X is a number with all the bits – zeroes and bit on given
|
||||
position – one.
|
||||
Where X is a number with all the bits - zeroes and bit on given
|
||||
position - one.
|
||||
|
||||
>>> flip_bit(0b101, 1) # 0b111
|
||||
7
|
||||
|
@ -79,7 +79,7 @@ def get_bit(number: int, position: int) -> int:
|
|||
Get the bit at the given position
|
||||
|
||||
Details: perform bitwise and for the given number and X,
|
||||
Where X is a number with all the bits – zeroes and bit on given position – one.
|
||||
Where X is a number with all the bits - zeroes and bit on given position - one.
|
||||
If the result is not equal to 0, then the bit on the given position is 1, else 0.
|
||||
|
||||
>>> get_bit(0b1010, 0)
|
||||
|
|
|
@ -101,9 +101,8 @@ def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool:
|
|||
state = True
|
||||
elif alive > 3:
|
||||
state = False
|
||||
else:
|
||||
if alive == 3:
|
||||
state = True
|
||||
elif alive == 3:
|
||||
state = True
|
||||
|
||||
return state
|
||||
|
||||
|
|
|
@ -24,6 +24,14 @@ def encrypt(plaintext: str, key: str) -> str:
|
|||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: plaintext is empty
|
||||
>>> encrypt("coffee is good as python", "")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: key is empty
|
||||
>>> encrypt(527.26, "TheAlgorithms")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: plaintext must be a string
|
||||
"""
|
||||
if not isinstance(plaintext, str):
|
||||
raise TypeError("plaintext must be a string")
|
||||
|
@ -80,6 +88,14 @@ def decrypt(ciphertext: str, key: str) -> str:
|
|||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: ciphertext must be a string
|
||||
>>> decrypt("", "TheAlgorithms")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: ciphertext is empty
|
||||
>>> decrypt("vvjfpk wj ohvp su ddylsv", 2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: key must be a string
|
||||
"""
|
||||
if not isinstance(ciphertext, str):
|
||||
raise TypeError("ciphertext must be a string")
|
||||
|
|
|
@ -206,20 +206,19 @@ def decrypt_caesar_with_chi_squared(
|
|||
|
||||
# Add the margin of error to the total chi squared statistic
|
||||
chi_squared_statistic += chi_letter_value
|
||||
else:
|
||||
if letter.lower() in frequencies:
|
||||
# Get the amount of times the letter occurs in the message
|
||||
occurrences = decrypted_with_shift.count(letter)
|
||||
elif letter.lower() in frequencies:
|
||||
# Get the amount of times the letter occurs in the message
|
||||
occurrences = decrypted_with_shift.count(letter)
|
||||
|
||||
# Get the excepcted amount of times the letter should appear based
|
||||
# on letter frequencies
|
||||
expected = frequencies[letter] * occurrences
|
||||
# Get the excepcted amount of times the letter should appear based
|
||||
# on letter frequencies
|
||||
expected = frequencies[letter] * occurrences
|
||||
|
||||
# Complete the chi squared statistic formula
|
||||
chi_letter_value = ((occurrences - expected) ** 2) / expected
|
||||
# Complete the chi squared statistic formula
|
||||
chi_letter_value = ((occurrences - expected) ** 2) / expected
|
||||
|
||||
# Add the margin of error to the total chi squared statistic
|
||||
chi_squared_statistic += chi_letter_value
|
||||
# Add the margin of error to the total chi squared statistic
|
||||
chi_squared_statistic += chi_letter_value
|
||||
|
||||
# Add the data to the chi_squared_statistic_values dictionary
|
||||
chi_squared_statistic_values[shift] = (
|
||||
|
|
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 numpy
|
||||
import numpy as np
|
||||
|
||||
from maths.greatest_common_divisor import greatest_common_divisor
|
||||
|
||||
|
@ -49,11 +49,11 @@ class HillCipher:
|
|||
# i.e. a total of 36 characters
|
||||
|
||||
# take x and return x % len(key_string)
|
||||
modulus = numpy.vectorize(lambda x: x % 36)
|
||||
modulus = np.vectorize(lambda x: x % 36)
|
||||
|
||||
to_int = numpy.vectorize(round)
|
||||
to_int = np.vectorize(round)
|
||||
|
||||
def __init__(self, encrypt_key: numpy.ndarray) -> None:
|
||||
def __init__(self, encrypt_key: np.ndarray) -> None:
|
||||
"""
|
||||
encrypt_key is an NxN numpy array
|
||||
"""
|
||||
|
@ -63,7 +63,7 @@ class HillCipher:
|
|||
|
||||
def replace_letters(self, letter: str) -> int:
|
||||
"""
|
||||
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher.replace_letters('T')
|
||||
19
|
||||
>>> hill_cipher.replace_letters('0')
|
||||
|
@ -73,7 +73,7 @@ class HillCipher:
|
|||
|
||||
def replace_digits(self, num: int) -> str:
|
||||
"""
|
||||
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher.replace_digits(19)
|
||||
'T'
|
||||
>>> hill_cipher.replace_digits(26)
|
||||
|
@ -83,10 +83,10 @@ class HillCipher:
|
|||
|
||||
def check_determinant(self) -> None:
|
||||
"""
|
||||
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher.check_determinant()
|
||||
"""
|
||||
det = round(numpy.linalg.det(self.encrypt_key))
|
||||
det = round(np.linalg.det(self.encrypt_key))
|
||||
|
||||
if det < 0:
|
||||
det = det % len(self.key_string)
|
||||
|
@ -101,7 +101,7 @@ class HillCipher:
|
|||
|
||||
def process_text(self, text: str) -> str:
|
||||
"""
|
||||
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher.process_text('Testing Hill Cipher')
|
||||
'TESTINGHILLCIPHERR'
|
||||
>>> hill_cipher.process_text('hello')
|
||||
|
@ -117,7 +117,7 @@ class HillCipher:
|
|||
|
||||
def encrypt(self, text: str) -> str:
|
||||
"""
|
||||
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher.encrypt('testing hill cipher')
|
||||
'WHXYJOLM9C6XT085LL'
|
||||
>>> hill_cipher.encrypt('hello')
|
||||
|
@ -129,7 +129,7 @@ class HillCipher:
|
|||
for i in range(0, len(text) - self.break_key + 1, self.break_key):
|
||||
batch = text[i : i + self.break_key]
|
||||
vec = [self.replace_letters(char) for char in batch]
|
||||
batch_vec = numpy.array([vec]).T
|
||||
batch_vec = np.array([vec]).T
|
||||
batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[
|
||||
0
|
||||
]
|
||||
|
@ -140,14 +140,14 @@ class HillCipher:
|
|||
|
||||
return encrypted
|
||||
|
||||
def make_decrypt_key(self) -> numpy.ndarray:
|
||||
def make_decrypt_key(self) -> np.ndarray:
|
||||
"""
|
||||
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher.make_decrypt_key()
|
||||
array([[ 6, 25],
|
||||
[ 5, 26]])
|
||||
"""
|
||||
det = round(numpy.linalg.det(self.encrypt_key))
|
||||
det = round(np.linalg.det(self.encrypt_key))
|
||||
|
||||
if det < 0:
|
||||
det = det % len(self.key_string)
|
||||
|
@ -158,16 +158,14 @@ class HillCipher:
|
|||
break
|
||||
|
||||
inv_key = (
|
||||
det_inv
|
||||
* numpy.linalg.det(self.encrypt_key)
|
||||
* numpy.linalg.inv(self.encrypt_key)
|
||||
det_inv * np.linalg.det(self.encrypt_key) * np.linalg.inv(self.encrypt_key)
|
||||
)
|
||||
|
||||
return self.to_int(self.modulus(inv_key))
|
||||
|
||||
def decrypt(self, text: str) -> str:
|
||||
"""
|
||||
>>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher = HillCipher(np.array([[2, 5], [1, 6]]))
|
||||
>>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL')
|
||||
'TESTINGHILLCIPHERR'
|
||||
>>> hill_cipher.decrypt('85FF00')
|
||||
|
@ -180,7 +178,7 @@ class HillCipher:
|
|||
for i in range(0, len(text) - self.break_key + 1, self.break_key):
|
||||
batch = text[i : i + self.break_key]
|
||||
vec = [self.replace_letters(char) for char in batch]
|
||||
batch_vec = numpy.array([vec]).T
|
||||
batch_vec = np.array([vec]).T
|
||||
batch_decrypted = self.modulus(decrypt_key.dot(batch_vec)).T.tolist()[0]
|
||||
decrypted_batch = "".join(
|
||||
self.replace_digits(num) for num in batch_decrypted
|
||||
|
@ -199,7 +197,7 @@ def main() -> None:
|
|||
row = [int(x) for x in input().split()]
|
||||
hill_matrix.append(row)
|
||||
|
||||
hc = HillCipher(numpy.array(hill_matrix))
|
||||
hc = HillCipher(np.array(hill_matrix))
|
||||
|
||||
print("Would you like to encrypt or decrypt some text? (1 or 2)")
|
||||
option = input("\n1. Encrypt\n2. Decrypt\n")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
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
|
||||
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
|
||||
|
|
|
@ -40,7 +40,7 @@ def build_tree(letters: list[Letter]) -> Letter | TreeNode:
|
|||
Run through the list of Letters and build the min heap
|
||||
for the Huffman Tree.
|
||||
"""
|
||||
response: list[Letter | TreeNode] = letters # type: ignore
|
||||
response: list[Letter | TreeNode] = list(letters)
|
||||
while len(response) > 1:
|
||||
left = response.pop(0)
|
||||
right = response.pop(0)
|
||||
|
@ -59,7 +59,7 @@ def traverse_tree(root: Letter | TreeNode, bitstring: str) -> list[Letter]:
|
|||
if isinstance(root, Letter):
|
||||
root.bitstring[root.letter] = bitstring
|
||||
return [root]
|
||||
treenode: TreeNode = root # type: ignore
|
||||
treenode: TreeNode = root
|
||||
letters = []
|
||||
letters += traverse_tree(treenode.left, bitstring + "0")
|
||||
letters += traverse_tree(treenode.right, bitstring + "1")
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
||||
|
@ -43,7 +43,7 @@ def add_key_to_lexicon(
|
|||
|
||||
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
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
|
@ -26,7 +26,7 @@ def read_file_binary(file_path: 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
|
||||
"""
|
||||
lexicon = {"0": "0", "1": "1"}
|
||||
|
|
|
@ -25,7 +25,7 @@ import numpy as np
|
|||
|
||||
# Importing the Keras libraries and packages
|
||||
import tensorflow as tf
|
||||
from tensorflow.keras import layers, models
|
||||
from keras import layers, models
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Initialising the CNN
|
||||
|
|
|
@ -19,7 +19,7 @@ def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float
|
|||
>>> root_mean_square_error(np.array([1, 2, 3]), np.array([6, 4, 2]))
|
||||
3.1622776601683795
|
||||
"""
|
||||
return np.sqrt(((original - reference) ** 2).mean())
|
||||
return float(np.sqrt(((original - reference) ** 2).mean()))
|
||||
|
||||
|
||||
def normalize_image(
|
||||
|
@ -141,7 +141,7 @@ def transform(
|
|||
|
||||
center_x, center_y = (x // 2 for x in kernel.shape)
|
||||
|
||||
# Use padded image when applying convolotion
|
||||
# Use padded image when applying convolution
|
||||
# to not go out of bounds of the original the image
|
||||
transformed = np.zeros(image.shape, dtype=np.uint8)
|
||||
padded = np.pad(image, 1, "constant", constant_values=constant)
|
||||
|
@ -273,7 +273,7 @@ def haralick_descriptors(matrix: np.ndarray) -> list[float]:
|
|||
>>> morphological = opening_filter(binary)
|
||||
>>> mask_1 = binary_mask(gray, morphological)[0]
|
||||
>>> concurrency = matrix_concurrency(mask_1, (0, 1))
|
||||
>>> haralick_descriptors(concurrency)
|
||||
>>> [float(f) for f in haralick_descriptors(concurrency)]
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
"""
|
||||
# Function np.indices could be used for bigger input types,
|
||||
|
@ -335,7 +335,7 @@ def get_descriptors(
|
|||
return np.concatenate(descriptors, axis=None)
|
||||
|
||||
|
||||
def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32:
|
||||
def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> float:
|
||||
"""
|
||||
Simple method for calculating the euclidean distance between two points,
|
||||
with type np.ndarray.
|
||||
|
@ -346,7 +346,7 @@ def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32:
|
|||
>>> euclidean(a, b)
|
||||
3.3166247903554
|
||||
"""
|
||||
return np.sqrt(np.sum(np.square(point_1 - point_2)))
|
||||
return float(np.sqrt(np.sum(np.square(point_1 - point_2))))
|
||||
|
||||
|
||||
def get_distances(descriptors: np.ndarray, base: int) -> list[tuple[int, float]]:
|
||||
|
|
|
@ -297,6 +297,12 @@ def weight_conversion(from_type: str, to_type: str, value: float) -> float:
|
|||
1.660540199e-23
|
||||
>>> weight_conversion("atomic-mass-unit","atomic-mass-unit",2)
|
||||
1.999999998903455
|
||||
>>> weight_conversion("slug", "kilogram", 1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid 'from_type' or 'to_type' value: 'slug', 'kilogram'
|
||||
Supported values are: kilogram, gram, milligram, metric-ton, long-ton, short-ton, \
|
||||
pound, stone, ounce, carrat, atomic-mass-unit
|
||||
"""
|
||||
if to_type not in KILOGRAM_CHART or from_type not in WEIGHT_TYPE_CHART:
|
||||
msg = (
|
||||
|
|
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]]
|
||||
if len(dplaces) == 0:
|
||||
return False ## Contradiction: no place for this value
|
||||
elif len(dplaces) == 1:
|
||||
# d can only be in one place in unit; assign it there
|
||||
if not assign(values, dplaces[0], d):
|
||||
return False
|
||||
# d can only be in one place in unit; assign it there
|
||||
elif len(dplaces) == 1 and not assign(values, dplaces[0], d):
|
||||
return False
|
||||
return values
|
||||
|
||||
|
||||
|
@ -151,7 +150,7 @@ def solve_all(grids, name="", showif=0.0):
|
|||
display(grid_values(grid))
|
||||
if values:
|
||||
display(values)
|
||||
print("(%.5f seconds)\n" % t)
|
||||
print(f"({t:.5f} seconds)\n")
|
||||
return (t, solved(values))
|
||||
|
||||
times, results = zip(*[time_solve(grid) for grid in grids])
|
||||
|
@ -173,7 +172,7 @@ def solved(values):
|
|||
|
||||
def from_file(filename, sep="\n"):
|
||||
"Parse a file into a list of strings, separated by sep."
|
||||
return open(filename).read().strip().split(sep) # noqa: SIM115
|
||||
return open(filename).read().strip().split(sep)
|
||||
|
||||
|
||||
def random_puzzle(assignments=17):
|
||||
|
@ -218,4 +217,4 @@ if __name__ == "__main__":
|
|||
start = time.monotonic()
|
||||
solve(puzzle)
|
||||
t = time.monotonic() - start
|
||||
print("Solved: %.5f sec" % t)
|
||||
print(f"Solved: {t:.5f} sec")
|
||||
|
|
|
@ -215,11 +215,11 @@ def del_node(root: MyNode, data: Any) -> MyNode | None:
|
|||
return root
|
||||
else:
|
||||
root.set_left(del_node(left_child, data))
|
||||
else: # root.get_data() < data
|
||||
if right_child is None:
|
||||
return root
|
||||
else:
|
||||
root.set_right(del_node(right_child, data))
|
||||
# root.get_data() < data
|
||||
elif right_child is None:
|
||||
return root
|
||||
else:
|
||||
root.set_right(del_node(right_child, data))
|
||||
|
||||
if get_height(right_child) - get_height(left_child) == 2:
|
||||
assert right_child is not None
|
||||
|
|
|
@ -85,7 +85,7 @@ class BinaryTree:
|
|||
"""
|
||||
return self._depth(self.root)
|
||||
|
||||
def _depth(self, node: Node | None) -> int: # noqa: UP007
|
||||
def _depth(self, node: Node | None) -> int:
|
||||
if not node:
|
||||
return 0
|
||||
return 1 + max(self._depth(node.left), self._depth(node.right))
|
||||
|
|
|
@ -185,12 +185,11 @@ class BinarySearchTree:
|
|||
break
|
||||
else:
|
||||
parent_node = parent_node.left
|
||||
elif parent_node.right is None:
|
||||
parent_node.right = new_node
|
||||
break
|
||||
else:
|
||||
if parent_node.right is None:
|
||||
parent_node.right = new_node
|
||||
break
|
||||
else:
|
||||
parent_node = parent_node.right
|
||||
parent_node = parent_node.right
|
||||
new_node.parent = parent_node
|
||||
|
||||
def insert(self, *values) -> Self:
|
||||
|
@ -295,9 +294,9 @@ class BinarySearchTree:
|
|||
predecessor = self.get_max(
|
||||
node.left
|
||||
) # Gets the max value of the left branch
|
||||
self.remove(predecessor.value) # type: ignore
|
||||
self.remove(predecessor.value) # type: ignore[union-attr]
|
||||
node.value = (
|
||||
predecessor.value # type: ignore
|
||||
predecessor.value # type: ignore[union-attr]
|
||||
) # Assigns the value to the node to delete and keep tree structure
|
||||
|
||||
def preorder_traverse(self, node: Node | None) -> Iterable:
|
||||
|
@ -337,7 +336,7 @@ def inorder(curr_node: Node | None) -> list[Node]:
|
|||
"""
|
||||
node_list = []
|
||||
if curr_node is not None:
|
||||
node_list = inorder(curr_node.left) + [curr_node] + inorder(curr_node.right)
|
||||
node_list = [*inorder(curr_node.left), curr_node, *inorder(curr_node.right)]
|
||||
return node_list
|
||||
|
||||
|
||||
|
|
|
@ -74,14 +74,13 @@ class BinarySearchTree:
|
|||
def _put(self, node: Node | None, label: int, parent: Node | None = None) -> Node:
|
||||
if node is None:
|
||||
node = Node(label, parent)
|
||||
elif label < node.label:
|
||||
node.left = self._put(node.left, label, node)
|
||||
elif label > node.label:
|
||||
node.right = self._put(node.right, label, node)
|
||||
else:
|
||||
if label < node.label:
|
||||
node.left = self._put(node.left, label, node)
|
||||
elif label > node.label:
|
||||
node.right = self._put(node.right, label, node)
|
||||
else:
|
||||
msg = f"Node with label {label} already exists"
|
||||
raise ValueError(msg)
|
||||
msg = f"Node with label {label} already exists"
|
||||
raise ValueError(msg)
|
||||
|
||||
return node
|
||||
|
||||
|
@ -106,11 +105,10 @@ class BinarySearchTree:
|
|||
if node is None:
|
||||
msg = f"Node with label {label} does not exist"
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
if label < node.label:
|
||||
node = self._search(node.left, label)
|
||||
elif label > node.label:
|
||||
node = self._search(node.right, label)
|
||||
elif label < node.label:
|
||||
node = self._search(node.left, label)
|
||||
elif label > node.label:
|
||||
node = self._search(node.right, label)
|
||||
|
||||
return node
|
||||
|
||||
|
|
|
@ -97,6 +97,8 @@ def level_order(root: Node | None) -> Generator[int, None, None]:
|
|||
"""
|
||||
Returns a list of nodes value from a whole binary tree in Level Order Traverse.
|
||||
Level Order traverse: Visit nodes of the tree level-by-level.
|
||||
>>> list(level_order(make_tree()))
|
||||
[1, 2, 3, 4, 5]
|
||||
"""
|
||||
|
||||
if root is None:
|
||||
|
@ -120,6 +122,10 @@ def get_nodes_from_left_to_right(
|
|||
"""
|
||||
Returns a list of nodes value from a particular level:
|
||||
Left to right direction of the binary tree.
|
||||
>>> list(get_nodes_from_left_to_right(make_tree(), 1))
|
||||
[1]
|
||||
>>> list(get_nodes_from_left_to_right(make_tree(), 2))
|
||||
[2, 3]
|
||||
"""
|
||||
|
||||
def populate_output(root: Node | None, level: int) -> Generator[int, None, None]:
|
||||
|
@ -140,10 +146,14 @@ def get_nodes_from_right_to_left(
|
|||
"""
|
||||
Returns a list of nodes value from a particular level:
|
||||
Right to left direction of the binary tree.
|
||||
>>> list(get_nodes_from_right_to_left(make_tree(), 1))
|
||||
[1]
|
||||
>>> list(get_nodes_from_right_to_left(make_tree(), 2))
|
||||
[3, 2]
|
||||
"""
|
||||
|
||||
def populate_output(root: Node | None, level: int) -> Generator[int, None, None]:
|
||||
if root is None:
|
||||
if not root:
|
||||
return
|
||||
if level == 1:
|
||||
yield root.data
|
||||
|
@ -158,6 +168,8 @@ def zigzag(root: Node | None) -> Generator[int, None, None]:
|
|||
"""
|
||||
ZigZag traverse:
|
||||
Returns a list of nodes value from left to right and right to left, alternatively.
|
||||
>>> list(zigzag(make_tree()))
|
||||
[1, 3, 2, 4, 5]
|
||||
"""
|
||||
if root is None:
|
||||
return
|
||||
|
|
|
@ -80,9 +80,9 @@ class Node:
|
|||
"""
|
||||
if self.left and (self.data < self.left.data or not self.left.is_sorted):
|
||||
return False
|
||||
if self.right and (self.data > self.right.data or not self.right.is_sorted):
|
||||
return False
|
||||
return True
|
||||
return not (
|
||||
self.right and (self.data > self.right.data or not self.right.is_sorted)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
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
|
||||
self.st[p] = self.fn(self.st[p * 2], self.st[p * 2 + 1])
|
||||
|
||||
def query(self, l: int, r: int) -> T | None: # noqa: E741
|
||||
def query(self, left: int, right: int) -> T | None:
|
||||
"""
|
||||
Get range query value in log(N) time
|
||||
:param l: left element index
|
||||
:param r: right element index
|
||||
:return: element combined in the range [l, r]
|
||||
:param left: left element index
|
||||
:param right: right element index
|
||||
:return: element combined in the range [left, right]
|
||||
|
||||
>>> st = SegmentTree([1, 2, 3, 4], lambda a, b: a + b)
|
||||
>>> st.query(0, 2)
|
||||
|
@ -104,15 +104,15 @@ class SegmentTree(Generic[T]):
|
|||
>>> st.query(2, 3)
|
||||
7
|
||||
"""
|
||||
l, r = l + self.N, r + self.N
|
||||
left, right = left + self.N, right + self.N
|
||||
|
||||
res: T | None = None
|
||||
while l <= r:
|
||||
if l % 2 == 1:
|
||||
res = self.st[l] if res is None else self.fn(res, self.st[l])
|
||||
if r % 2 == 0:
|
||||
res = self.st[r] if res is None else self.fn(res, self.st[r])
|
||||
l, r = (l + 1) // 2, (r - 1) // 2
|
||||
while left <= right:
|
||||
if left % 2 == 1:
|
||||
res = self.st[left] if res is None else self.fn(res, self.st[left])
|
||||
if right % 2 == 0:
|
||||
res = self.st[right] if res is None else self.fn(res, self.st[right])
|
||||
left, right = (left + 1) // 2, (right - 1) // 2
|
||||
return res
|
||||
|
||||
|
||||
|
|
|
@ -31,8 +31,7 @@ def binomial_coefficient(n: int, k: int) -> int:
|
|||
"""
|
||||
result = 1 # To kept the Calculated Value
|
||||
# Since C(n, k) = C(n, n-k)
|
||||
if k > (n - k):
|
||||
k = n - k
|
||||
k = min(k, n - k)
|
||||
# Calculate C(n,k)
|
||||
for i in range(k):
|
||||
result *= n - i
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
"""
|
||||
psf/black : true
|
||||
ruff : passed
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
|
@ -17,7 +12,7 @@ class RedBlackTree:
|
|||
and slower for reading in the average case, though, because they're
|
||||
both balanced binary search trees, both will get the same asymptotic
|
||||
performance.
|
||||
To read more about them, https://en.wikipedia.org/wiki/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
|
||||
terms of the size of the tree.
|
||||
"""
|
||||
|
@ -107,12 +102,11 @@ class RedBlackTree:
|
|||
else:
|
||||
self.left = RedBlackTree(label, 1, self)
|
||||
self.left._insert_repair()
|
||||
elif self.right:
|
||||
self.right.insert(label)
|
||||
else:
|
||||
if self.right:
|
||||
self.right.insert(label)
|
||||
else:
|
||||
self.right = RedBlackTree(label, 1, self)
|
||||
self.right._insert_repair()
|
||||
self.right = RedBlackTree(label, 1, self)
|
||||
self.right._insert_repair()
|
||||
return self.parent or self
|
||||
|
||||
def _insert_repair(self) -> None:
|
||||
|
@ -153,7 +147,7 @@ class RedBlackTree:
|
|||
self.grandparent.color = 1
|
||||
self.grandparent._insert_repair()
|
||||
|
||||
def remove(self, label: int) -> RedBlackTree: # noqa: PLR0912
|
||||
def remove(self, label: int) -> RedBlackTree:
|
||||
"""Remove label from this tree."""
|
||||
if self.label == label:
|
||||
if self.left and self.right:
|
||||
|
@ -178,36 +172,34 @@ class RedBlackTree:
|
|||
self.parent.left = None
|
||||
else:
|
||||
self.parent.right = None
|
||||
else:
|
||||
# The node is black
|
||||
if child is None:
|
||||
# This node and its child are black
|
||||
if self.parent is None:
|
||||
# The tree is now empty
|
||||
return RedBlackTree(None)
|
||||
else:
|
||||
self._remove_repair()
|
||||
if self.is_left():
|
||||
self.parent.left = None
|
||||
else:
|
||||
self.parent.right = None
|
||||
self.parent = None
|
||||
# The node is black
|
||||
elif child is None:
|
||||
# This node and its child are black
|
||||
if self.parent is None:
|
||||
# The tree is now empty
|
||||
return RedBlackTree(None)
|
||||
else:
|
||||
# This node is black and its child is red
|
||||
# Move the child node here and make it black
|
||||
self.label = child.label
|
||||
self.left = child.left
|
||||
self.right = child.right
|
||||
if self.left:
|
||||
self.left.parent = self
|
||||
if self.right:
|
||||
self.right.parent = self
|
||||
self._remove_repair()
|
||||
if self.is_left():
|
||||
self.parent.left = None
|
||||
else:
|
||||
self.parent.right = None
|
||||
self.parent = None
|
||||
else:
|
||||
# This node is black and its child is red
|
||||
# Move the child node here and make it black
|
||||
self.label = child.label
|
||||
self.left = child.left
|
||||
self.right = child.right
|
||||
if self.left:
|
||||
self.left.parent = self
|
||||
if self.right:
|
||||
self.right.parent = self
|
||||
elif self.label is not None and self.label > label:
|
||||
if self.left:
|
||||
self.left.remove(label)
|
||||
else:
|
||||
if self.right:
|
||||
self.right.remove(label)
|
||||
elif self.right:
|
||||
self.right.remove(label)
|
||||
return self.parent or self
|
||||
|
||||
def _remove_repair(self) -> None:
|
||||
|
@ -324,9 +316,7 @@ class RedBlackTree:
|
|||
return False
|
||||
if self.left and not self.left.check_coloring():
|
||||
return False
|
||||
if self.right and not self.right.check_coloring():
|
||||
return False
|
||||
return True
|
||||
return not (self.right and not self.right.check_coloring())
|
||||
|
||||
def black_height(self) -> int | None:
|
||||
"""Returns the number of black nodes from this node to the
|
||||
|
@ -369,11 +359,10 @@ class RedBlackTree:
|
|||
return None
|
||||
else:
|
||||
return self.right.search(label)
|
||||
elif self.left is None:
|
||||
return None
|
||||
else:
|
||||
if self.left is None:
|
||||
return None
|
||||
else:
|
||||
return self.left.search(label)
|
||||
return self.left.search(label)
|
||||
|
||||
def floor(self, label: int) -> int | None:
|
||||
"""Returns the largest element in this tree which is at most label.
|
||||
|
@ -565,9 +554,7 @@ def test_rotations() -> bool:
|
|||
right_rot.right.right = RedBlackTree(10, parent=right_rot.right)
|
||||
right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.right)
|
||||
right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right)
|
||||
if tree != right_rot:
|
||||
return False
|
||||
return True
|
||||
return tree == right_rot
|
||||
|
||||
|
||||
def test_insertion_speed() -> bool:
|
||||
|
@ -610,13 +597,11 @@ def test_insert_and_search() -> bool:
|
|||
tree.insert(12)
|
||||
tree.insert(10)
|
||||
tree.insert(11)
|
||||
if 5 in tree or -6 in tree or -10 in tree or 13 in tree:
|
||||
if any(i in tree for i in (5, -6, -10, 13)):
|
||||
# Found something not in there
|
||||
return False
|
||||
if not (11 in tree and 12 in tree and -8 in tree and 0 in tree):
|
||||
# Didn't find something in there
|
||||
return False
|
||||
return True
|
||||
# Find all these things in there
|
||||
return all(i in tree for i in (11, 12, -8, 0))
|
||||
|
||||
|
||||
def test_insert_delete() -> bool:
|
||||
|
@ -638,9 +623,7 @@ def test_insert_delete() -> bool:
|
|||
tree = tree.remove(9)
|
||||
if not tree.check_color_properties():
|
||||
return False
|
||||
if list(tree.inorder_traverse()) != [-8, 0, 4, 8, 10, 11, 12]:
|
||||
return False
|
||||
return True
|
||||
return list(tree.inorder_traverse()) == [-8, 0, 4, 8, 10, 11, 12]
|
||||
|
||||
|
||||
def test_floor_ceil() -> bool:
|
||||
|
@ -668,9 +651,7 @@ def test_min_max() -> bool:
|
|||
tree.insert(24)
|
||||
tree.insert(20)
|
||||
tree.insert(22)
|
||||
if tree.get_max() != 22 or tree.get_min() != -16:
|
||||
return False
|
||||
return True
|
||||
return not (tree.get_max() != 22 or tree.get_min() != -16)
|
||||
|
||||
|
||||
def test_tree_traversal() -> bool:
|
||||
|
@ -686,9 +667,7 @@ def test_tree_traversal() -> bool:
|
|||
return False
|
||||
if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]:
|
||||
return False
|
||||
if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]:
|
||||
return False
|
||||
return True
|
||||
return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0]
|
||||
|
||||
|
||||
def test_tree_chaining() -> bool:
|
||||
|
@ -699,9 +678,7 @@ def test_tree_chaining() -> bool:
|
|||
return False
|
||||
if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]:
|
||||
return False
|
||||
if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]:
|
||||
return False
|
||||
return True
|
||||
return list(tree.postorder_traverse()) == [-16, 8, 20, 24, 22, 16, 0]
|
||||
|
||||
|
||||
def print_results(msg: str, passes: bool) -> None:
|
||||
|
|
|
@ -35,13 +35,13 @@ class SegmentTree:
|
|||
"""
|
||||
return idx * 2 + 1
|
||||
|
||||
def build(self, idx, l, r): # noqa: E741
|
||||
if l == r:
|
||||
self.st[idx] = self.A[l]
|
||||
def build(self, idx, left, right):
|
||||
if left == right:
|
||||
self.st[idx] = self.A[left]
|
||||
else:
|
||||
mid = (l + r) // 2
|
||||
self.build(self.left(idx), l, mid)
|
||||
self.build(self.right(idx), mid + 1, r)
|
||||
mid = (left + right) // 2
|
||||
self.build(self.left(idx), left, mid)
|
||||
self.build(self.right(idx), mid + 1, right)
|
||||
self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)])
|
||||
|
||||
def update(self, a, b, val):
|
||||
|
@ -56,18 +56,18 @@ class SegmentTree:
|
|||
"""
|
||||
return self.update_recursive(1, 0, self.N - 1, a - 1, b - 1, val)
|
||||
|
||||
def update_recursive(self, idx, l, r, a, b, val): # noqa: E741
|
||||
def update_recursive(self, idx, left, right, a, b, val):
|
||||
"""
|
||||
update(1, 1, N, a, b, v) for update val v to [a,b]
|
||||
"""
|
||||
if r < a or l > b:
|
||||
if right < a or left > b:
|
||||
return True
|
||||
if l == r:
|
||||
if left == right:
|
||||
self.st[idx] = val
|
||||
return True
|
||||
mid = (l + r) // 2
|
||||
self.update_recursive(self.left(idx), l, mid, a, b, val)
|
||||
self.update_recursive(self.right(idx), mid + 1, r, a, b, val)
|
||||
mid = (left + right) // 2
|
||||
self.update_recursive(self.left(idx), left, mid, a, b, val)
|
||||
self.update_recursive(self.right(idx), mid + 1, right, a, b, val)
|
||||
self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)])
|
||||
return True
|
||||
|
||||
|
@ -83,22 +83,22 @@ class SegmentTree:
|
|||
"""
|
||||
return self.query_recursive(1, 0, self.N - 1, a - 1, b - 1)
|
||||
|
||||
def query_recursive(self, idx, l, r, a, b): # noqa: E741
|
||||
def query_recursive(self, idx, left, right, a, b):
|
||||
"""
|
||||
query(1, 1, N, a, b) for query max of [a,b]
|
||||
"""
|
||||
if r < a or l > b:
|
||||
if right < a or left > b:
|
||||
return -math.inf
|
||||
if l >= a and r <= b:
|
||||
if left >= a and right <= b:
|
||||
return self.st[idx]
|
||||
mid = (l + r) // 2
|
||||
q1 = self.query_recursive(self.left(idx), l, mid, a, b)
|
||||
q2 = self.query_recursive(self.right(idx), mid + 1, r, a, b)
|
||||
mid = (left + right) // 2
|
||||
q1 = self.query_recursive(self.left(idx), left, mid, a, b)
|
||||
q2 = self.query_recursive(self.right(idx), mid + 1, right, a, b)
|
||||
return max(q1, q2)
|
||||
|
||||
def show_data(self):
|
||||
show_list = []
|
||||
for i in range(1, N + 1):
|
||||
for i in range(1, self.N + 1):
|
||||
show_list += [self.query(i, i)]
|
||||
print(show_list)
|
||||
|
||||
|
|
|
@ -13,7 +13,21 @@ from dataclasses import dataclass
|
|||
@dataclass
|
||||
class Node:
|
||||
"""
|
||||
A Node has data variable and pointers to Nodes to its left and right.
|
||||
A Node represents an element of a binary tree, which contains:
|
||||
|
||||
Attributes:
|
||||
data: The value stored in the node (int).
|
||||
left: Pointer to the left child node (Node or None).
|
||||
right: Pointer to the right child node (Node or None).
|
||||
|
||||
Example:
|
||||
>>> node = Node(1, Node(2), Node(3))
|
||||
>>> node.data
|
||||
1
|
||||
>>> node.left.data
|
||||
2
|
||||
>>> node.right.data
|
||||
3
|
||||
"""
|
||||
|
||||
data: int
|
||||
|
@ -24,12 +38,25 @@ class Node:
|
|||
def make_symmetric_tree() -> Node:
|
||||
r"""
|
||||
Create a symmetric tree for testing.
|
||||
|
||||
The tree looks like this:
|
||||
1
|
||||
/ \
|
||||
2 2
|
||||
/ \ / \
|
||||
3 4 4 3
|
||||
|
||||
Returns:
|
||||
Node: Root node of a symmetric tree.
|
||||
|
||||
Example:
|
||||
>>> tree = make_symmetric_tree()
|
||||
>>> tree.data
|
||||
1
|
||||
>>> tree.left.data == tree.right.data
|
||||
True
|
||||
>>> tree.left.left.data == tree.right.right.data
|
||||
True
|
||||
"""
|
||||
root = Node(1)
|
||||
root.left = Node(2)
|
||||
|
@ -43,13 +70,26 @@ def make_symmetric_tree() -> Node:
|
|||
|
||||
def make_asymmetric_tree() -> Node:
|
||||
r"""
|
||||
Create a asymmetric tree for testing.
|
||||
Create an asymmetric tree for testing.
|
||||
|
||||
The tree looks like this:
|
||||
1
|
||||
/ \
|
||||
2 2
|
||||
/ \ / \
|
||||
3 4 3 4
|
||||
|
||||
Returns:
|
||||
Node: Root node of an asymmetric tree.
|
||||
|
||||
Example:
|
||||
>>> tree = make_asymmetric_tree()
|
||||
>>> tree.data
|
||||
1
|
||||
>>> tree.left.data == tree.right.data
|
||||
True
|
||||
>>> tree.left.left.data == tree.right.right.data
|
||||
False
|
||||
"""
|
||||
root = Node(1)
|
||||
root.left = Node(2)
|
||||
|
@ -63,7 +103,15 @@ def make_asymmetric_tree() -> Node:
|
|||
|
||||
def is_symmetric_tree(tree: Node) -> bool:
|
||||
"""
|
||||
Test cases for is_symmetric_tree function
|
||||
Check if a binary tree is symmetric (i.e., a mirror of itself).
|
||||
|
||||
Parameters:
|
||||
tree: The root node of the binary tree.
|
||||
|
||||
Returns:
|
||||
bool: True if the tree is symmetric, False otherwise.
|
||||
|
||||
Example:
|
||||
>>> is_symmetric_tree(make_symmetric_tree())
|
||||
True
|
||||
>>> is_symmetric_tree(make_asymmetric_tree())
|
||||
|
@ -76,8 +124,17 @@ def is_symmetric_tree(tree: Node) -> bool:
|
|||
|
||||
def is_mirror(left: Node | None, right: Node | None) -> bool:
|
||||
"""
|
||||
Check if two subtrees are mirror images of each other.
|
||||
|
||||
Parameters:
|
||||
left: The root node of the left subtree.
|
||||
right: The root node of the right subtree.
|
||||
|
||||
Returns:
|
||||
bool: True if the two subtrees are mirrors of each other, False otherwise.
|
||||
|
||||
Example:
|
||||
>>> tree1 = make_symmetric_tree()
|
||||
>>> tree1.right.right = Node(3)
|
||||
>>> is_mirror(tree1.left, tree1.right)
|
||||
True
|
||||
>>> tree2 = make_asymmetric_tree()
|
||||
|
@ -91,7 +148,7 @@ def is_mirror(left: Node | None, right: Node | None) -> bool:
|
|||
# One side is empty while the other is not, which is not symmetric.
|
||||
return False
|
||||
if left.data == right.data:
|
||||
# The values match, so check the subtree
|
||||
# The values match, so check the subtrees recursively.
|
||||
return is_mirror(left.left, right.right) and is_mirror(left.right, right.left)
|
||||
return False
|
||||
|
||||
|
|
|
@ -39,26 +39,23 @@ def split(root: Node | None, value: int) -> tuple[Node | None, Node | None]:
|
|||
Left tree contains all values less than split value.
|
||||
Right tree contains all values greater or equal, than split value
|
||||
"""
|
||||
if root is None: # None tree is split into 2 Nones
|
||||
return None, None
|
||||
elif root.value is None:
|
||||
if root is None or root.value is None: # None tree is split into 2 Nones
|
||||
return None, None
|
||||
elif value < root.value:
|
||||
"""
|
||||
Right tree's root will be current node.
|
||||
Now we split(with the same value) current node's left son
|
||||
Left tree: left part of that split
|
||||
Right tree's left son: right part of that split
|
||||
"""
|
||||
left, root.left = split(root.left, value)
|
||||
return left, root
|
||||
else:
|
||||
if value < root.value:
|
||||
"""
|
||||
Right tree's root will be current node.
|
||||
Now we split(with the same value) current node's left son
|
||||
Left tree: left part of that split
|
||||
Right tree's left son: right part of that split
|
||||
"""
|
||||
left, root.left = split(root.left, value)
|
||||
return left, root
|
||||
else:
|
||||
"""
|
||||
Just symmetric to previous case
|
||||
"""
|
||||
root.right, right = split(root.right, value)
|
||||
return root, right
|
||||
"""
|
||||
Just symmetric to previous case
|
||||
"""
|
||||
root.right, right = split(root.right, value)
|
||||
return root, right
|
||||
|
||||
|
||||
def merge(left: Node | None, right: Node | None) -> Node | None:
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
from abc import abstractmethod
|
||||
|
||||
from .number_theory.prime_numbers import next_prime
|
||||
|
||||
|
||||
|
@ -173,6 +175,7 @@ class HashTable:
|
|||
self.values[key] = data
|
||||
self._keys[key] = data
|
||||
|
||||
@abstractmethod
|
||||
def _collision_resolution(self, key, data=None):
|
||||
"""
|
||||
This method is a type of open addressing which is used for handling collision.
|
||||
|
|
|
@ -11,7 +11,7 @@ class QuadraticProbing(HashTable):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _collision_resolution(self, key, data=None):
|
||||
def _collision_resolution(self, key, data=None): # noqa: ARG002
|
||||
"""
|
||||
Quadratic probing is an open addressing scheme used for resolving
|
||||
collisions in hash table.
|
||||
|
|
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
|
||||
|
||||
Deleting - delete() test
|
||||
>>> [first_heap.delete_min() for _ in range(20)]
|
||||
>>> [int(first_heap.delete_min()) for _ in range(20)]
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
|
||||
|
||||
Create a new Heap
|
||||
|
@ -118,7 +118,7 @@ class BinomialHeap:
|
|||
values in merged heap; (merge is inplace)
|
||||
>>> results = []
|
||||
>>> while not first_heap.is_empty():
|
||||
... results.append(first_heap.delete_min())
|
||||
... results.append(int(first_heap.delete_min()))
|
||||
>>> results
|
||||
[17, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 34]
|
||||
"""
|
||||
|
@ -354,7 +354,7 @@ class BinomialHeap:
|
|||
# Merge heaps
|
||||
self.merge_heaps(new_heap)
|
||||
|
||||
return min_value
|
||||
return int(min_value)
|
||||
|
||||
def pre_order(self):
|
||||
"""
|
||||
|
|
|
@ -38,13 +38,12 @@ class BinaryHeap:
|
|||
def __swap_down(self, i: int) -> None:
|
||||
"""Swap the element down"""
|
||||
while self.__size >= 2 * i:
|
||||
if 2 * i + 1 > self.__size:
|
||||
if 2 * i + 1 > self.__size: # noqa: SIM114
|
||||
bigger_child = 2 * i
|
||||
elif self.__heap[2 * i] > self.__heap[2 * i + 1]:
|
||||
bigger_child = 2 * i
|
||||
else:
|
||||
if self.__heap[2 * i] > self.__heap[2 * i + 1]:
|
||||
bigger_child = 2 * i
|
||||
else:
|
||||
bigger_child = 2 * i + 1
|
||||
bigger_child = 2 * i + 1
|
||||
temporary = self.__heap[i]
|
||||
if self.__heap[i] < self.__heap[bigger_child]:
|
||||
self.__heap[i] = self.__heap[bigger_child]
|
||||
|
|
|
@ -66,14 +66,14 @@ class MinHeap:
|
|||
# this is min-heapify method
|
||||
def sift_down(self, idx, array):
|
||||
while True:
|
||||
l = self.get_left_child_idx(idx) # noqa: E741
|
||||
r = self.get_right_child_idx(idx)
|
||||
left = self.get_left_child_idx(idx)
|
||||
right = self.get_right_child_idx(idx)
|
||||
|
||||
smallest = idx
|
||||
if l < len(array) and array[l] < array[idx]:
|
||||
smallest = l
|
||||
if r < len(array) and array[r] < array[smallest]:
|
||||
smallest = r
|
||||
if left < len(array) and array[left] < array[idx]:
|
||||
smallest = left
|
||||
if right < len(array) and array[right] < array[smallest]:
|
||||
smallest = right
|
||||
|
||||
if smallest != idx:
|
||||
array[idx], array[smallest] = array[smallest], array[idx]
|
||||
|
|
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):
|
||||
node = self
|
||||
visited = []
|
||||
visited = set()
|
||||
while node:
|
||||
if node in visited:
|
||||
raise ContainsLoopError
|
||||
visited.append(node)
|
||||
visited.add(node)
|
||||
yield node.data
|
||||
node = node.next_node
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ def insert_node(head: Node | None, data: int) -> Node:
|
|||
while temp_node.next_node:
|
||||
temp_node = temp_node.next_node
|
||||
|
||||
temp_node.next_node = new_node # type: ignore
|
||||
temp_node.next_node = new_node
|
||||
return head
|
||||
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ https://epaperpress.com/sortsearch/download/skiplist.pdf
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import pairwise
|
||||
from random import random
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
|
@ -389,7 +390,7 @@ def test_delete_doesnt_leave_dead_nodes():
|
|||
|
||||
def test_iter_always_yields_sorted_values():
|
||||
def is_sorted(lst):
|
||||
return all(next_item >= item for item, next_item in zip(lst, lst[1:]))
|
||||
return all(next_item >= item for item, next_item in pairwise(lst))
|
||||
|
||||
skip_list = SkipList()
|
||||
for i in range(10):
|
||||
|
|
|
@ -25,6 +25,7 @@ class CircularQueue:
|
|||
|
||||
def is_empty(self) -> bool:
|
||||
"""
|
||||
Checks whether the queue is empty or not
|
||||
>>> cq = CircularQueue(5)
|
||||
>>> cq.is_empty()
|
||||
True
|
||||
|
@ -35,6 +36,7 @@ class CircularQueue:
|
|||
|
||||
def first(self):
|
||||
"""
|
||||
Returns the first element of the queue
|
||||
>>> cq = CircularQueue(5)
|
||||
>>> cq.first()
|
||||
False
|
||||
|
@ -45,7 +47,8 @@ class CircularQueue:
|
|||
|
||||
def enqueue(self, data):
|
||||
"""
|
||||
This function insert an element in the queue using self.rear value as an index
|
||||
This function inserts an element at the end of the queue using self.rear value
|
||||
as an index.
|
||||
>>> cq = CircularQueue(5)
|
||||
>>> cq.enqueue("A") # doctest: +ELLIPSIS
|
||||
<data_structures.queue.circular_queue.CircularQueue object at ...
|
||||
|
@ -67,7 +70,7 @@ class CircularQueue:
|
|||
def dequeue(self):
|
||||
"""
|
||||
This function removes an element from the queue using on self.front value as an
|
||||
index
|
||||
index and returns it
|
||||
>>> cq = CircularQueue(5)
|
||||
>>> cq.dequeue()
|
||||
Traceback (most recent call last):
|
||||
|
|
|
@ -39,7 +39,7 @@ class CircularQueueLinkedList:
|
|||
|
||||
def is_empty(self) -> bool:
|
||||
"""
|
||||
Checks where the queue is empty or not
|
||||
Checks whether the queue is empty or not
|
||||
>>> cq = CircularQueueLinkedList()
|
||||
>>> cq.is_empty()
|
||||
True
|
||||
|
|
|
@ -19,9 +19,10 @@ def balanced_parentheses(parentheses: str) -> bool:
|
|||
for bracket in parentheses:
|
||||
if bracket in bracket_pairs:
|
||||
stack.push(bracket)
|
||||
elif bracket in (")", "]", "}"):
|
||||
if stack.is_empty() or bracket_pairs[stack.pop()] != bracket:
|
||||
return False
|
||||
elif bracket in (")", "]", "}") and (
|
||||
stack.is_empty() or bracket_pairs[stack.pop()] != bracket
|
||||
):
|
||||
return False
|
||||
return stack.is_empty()
|
||||
|
||||
|
||||
|
|
|
@ -95,13 +95,12 @@ def infix_2_postfix(infix: str) -> str:
|
|||
while stack[-1] != "(":
|
||||
post_fix.append(stack.pop()) # Pop stack & add the content to Postfix
|
||||
stack.pop()
|
||||
else:
|
||||
if len(stack) == 0:
|
||||
stack.append(x) # If stack is empty, push x to stack
|
||||
else: # while priority of x is not > priority of element in the stack
|
||||
while stack and stack[-1] != "(" and priority[x] <= priority[stack[-1]]:
|
||||
post_fix.append(stack.pop()) # pop stack & add to Postfix
|
||||
stack.append(x) # push x to stack
|
||||
elif len(stack) == 0:
|
||||
stack.append(x) # If stack is empty, push x to stack
|
||||
else: # while priority of x is not > priority of element in the stack
|
||||
while stack and stack[-1] != "(" and priority[x] <= priority[stack[-1]]:
|
||||
post_fix.append(stack.pop()) # pop stack & add to Postfix
|
||||
stack.append(x) # push x to stack
|
||||
|
||||
print(
|
||||
x.center(8),
|
||||
|
|
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]:
|
||||
"""
|
||||
Get the Next Greatest Element (NGE) for all elements in a list.
|
||||
Maximum element present after the current one which is also greater than the
|
||||
current one.
|
||||
Get the Next Greatest Element (NGE) for each element in the array
|
||||
by checking all subsequent elements to find the next greater one.
|
||||
|
||||
This is a brute-force implementation, and it has a time complexity
|
||||
of O(n^2), where n is the size of the array.
|
||||
|
||||
Args:
|
||||
arr: List of numbers for which the NGE is calculated.
|
||||
|
||||
Returns:
|
||||
List containing the next greatest elements. If no
|
||||
greater element is found, -1 is placed in the result.
|
||||
|
||||
Example:
|
||||
>>> next_greatest_element_slow(arr) == expect
|
||||
True
|
||||
"""
|
||||
|
@ -28,9 +39,21 @@ def next_greatest_element_slow(arr: list[float]) -> list[float]:
|
|||
|
||||
def next_greatest_element_fast(arr: list[float]) -> list[float]:
|
||||
"""
|
||||
Like next_greatest_element_slow() but changes the loops to use
|
||||
enumerate() instead of range(len()) for the outer loop and
|
||||
for in a slice of arr for the inner loop.
|
||||
Find the Next Greatest Element (NGE) for each element in the array
|
||||
using a more readable approach. This implementation utilizes
|
||||
enumerate() for the outer loop and slicing for the inner loop.
|
||||
|
||||
While this improves readability over next_greatest_element_slow(),
|
||||
it still has a time complexity of O(n^2).
|
||||
|
||||
Args:
|
||||
arr: List of numbers for which the NGE is calculated.
|
||||
|
||||
Returns:
|
||||
List containing the next greatest elements. If no
|
||||
greater element is found, -1 is placed in the result.
|
||||
|
||||
Example:
|
||||
>>> next_greatest_element_fast(arr) == expect
|
||||
True
|
||||
"""
|
||||
|
@ -47,14 +70,23 @@ def next_greatest_element_fast(arr: list[float]) -> list[float]:
|
|||
|
||||
def next_greatest_element(arr: list[float]) -> list[float]:
|
||||
"""
|
||||
Get the Next Greatest Element (NGE) for all elements in a list.
|
||||
Maximum element present after the current one which is also greater than the
|
||||
current one.
|
||||
Efficient solution to find the Next Greatest Element (NGE) for all elements
|
||||
using a stack. The time complexity is reduced to O(n), making it suitable
|
||||
for larger arrays.
|
||||
|
||||
A naive way to solve this is to take two loops and check for the next bigger
|
||||
number but that will make the time complexity as O(n^2). The better way to solve
|
||||
this would be to use a stack to keep track of maximum number giving a linear time
|
||||
solution.
|
||||
The stack keeps track of elements for which the next greater element hasn't
|
||||
been found yet. By iterating through the array in reverse (from the last
|
||||
element to the first), the stack is used to efficiently determine the next
|
||||
greatest element for each element.
|
||||
|
||||
Args:
|
||||
arr: List of numbers for which the NGE is calculated.
|
||||
|
||||
Returns:
|
||||
List containing the next greatest elements. If no
|
||||
greater element is found, -1 is placed in the result.
|
||||
|
||||
Example:
|
||||
>>> next_greatest_element(arr) == expect
|
||||
True
|
||||
"""
|
||||
|
|
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
|
||||
elif remaining_word != "":
|
||||
return incoming_node.delete(remaining_word)
|
||||
# If it is not a leaf, we don't have to delete
|
||||
elif not incoming_node.is_leaf:
|
||||
return False
|
||||
else:
|
||||
# If it is not a leaf, we don't have to delete
|
||||
if not incoming_node.is_leaf:
|
||||
return False
|
||||
# We delete the nodes if no edges go from it
|
||||
if len(incoming_node.nodes) == 0:
|
||||
del self.nodes[word[0]]
|
||||
# We merge the current node with its only child
|
||||
if len(self.nodes) == 1 and not self.is_leaf:
|
||||
merging_node = next(iter(self.nodes.values()))
|
||||
self.is_leaf = merging_node.is_leaf
|
||||
self.prefix += merging_node.prefix
|
||||
self.nodes = merging_node.nodes
|
||||
# If there is more than 1 edge, we just mark it as non-leaf
|
||||
elif len(incoming_node.nodes) > 1:
|
||||
incoming_node.is_leaf = False
|
||||
# If there is 1 edge, we merge it with its child
|
||||
else:
|
||||
# We delete the nodes if no edges go from it
|
||||
if len(incoming_node.nodes) == 0:
|
||||
del self.nodes[word[0]]
|
||||
# We merge the current node with its only child
|
||||
if len(self.nodes) == 1 and not self.is_leaf:
|
||||
merging_node = next(iter(self.nodes.values()))
|
||||
self.is_leaf = merging_node.is_leaf
|
||||
self.prefix += merging_node.prefix
|
||||
self.nodes = merging_node.nodes
|
||||
# If there is more than 1 edge, we just mark it as non-leaf
|
||||
elif len(incoming_node.nodes) > 1:
|
||||
incoming_node.is_leaf = False
|
||||
# If there is 1 edge, we merge it with its child
|
||||
else:
|
||||
merging_node = next(iter(incoming_node.nodes.values()))
|
||||
incoming_node.is_leaf = merging_node.is_leaf
|
||||
incoming_node.prefix += merging_node.prefix
|
||||
incoming_node.nodes = merging_node.nodes
|
||||
merging_node = next(iter(incoming_node.nodes.values()))
|
||||
incoming_node.is_leaf = merging_node.is_leaf
|
||||
incoming_node.prefix += merging_node.prefix
|
||||
incoming_node.nodes = merging_node.nodes
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
def print_tree(self, height: int = 0) -> None:
|
||||
"""Print the tree
|
||||
|
|
|
@ -74,9 +74,9 @@ def detect_high_low_threshold(
|
|||
image_shape, destination, threshold_low, threshold_high, weak, strong
|
||||
):
|
||||
"""
|
||||
High-Low threshold detection. If an edge 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
|
||||
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
|
||||
an edge pixel's value is smaller than the low threshold value, it will be
|
||||
suppressed.
|
||||
|
|
|
@ -182,7 +182,7 @@ class IndexCalculation:
|
|||
Atmospherically Resistant Vegetation Index 2
|
||||
https://www.indexdatabase.de/db/i-single.php?id=396
|
||||
:return: index
|
||||
−0.18+1.17*(self.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)))
|
||||
|
||||
|
|
|
@ -54,8 +54,7 @@ def dis_between_closest_pair(points, points_counts, min_dis=float("inf")):
|
|||
for i in range(points_counts - 1):
|
||||
for j in range(i + 1, points_counts):
|
||||
current_dis = euclidean_distance_sqr(points[i], points[j])
|
||||
if current_dis < min_dis:
|
||||
min_dis = current_dis
|
||||
min_dis = min(min_dis, current_dis)
|
||||
return min_dis
|
||||
|
||||
|
||||
|
@ -76,8 +75,7 @@ def dis_between_closest_in_strip(points, points_counts, min_dis=float("inf")):
|
|||
for i in range(min(6, points_counts - 1), points_counts):
|
||||
for j in range(max(0, i - 6), i):
|
||||
current_dis = euclidean_distance_sqr(points[i], points[j])
|
||||
if current_dis < min_dis:
|
||||
min_dis = current_dis
|
||||
min_dis = min(min_dis, current_dis)
|
||||
return min_dis
|
||||
|
||||
|
||||
|
|
|
@ -274,14 +274,13 @@ def convex_hull_bf(points: list[Point]) -> list[Point]:
|
|||
points_left_of_ij = True
|
||||
elif det_k < 0:
|
||||
points_right_of_ij = True
|
||||
else:
|
||||
# point[i], point[j], point[k] all lie on a straight line
|
||||
# if point[k] is to the left of point[i] or it's to the
|
||||
# right of point[j], then point[i], point[j] cannot be
|
||||
# part of the convex hull of A
|
||||
if points[k] < points[i] or points[k] > points[j]:
|
||||
ij_part_of_convex_hull = False
|
||||
break
|
||||
# point[i], point[j], point[k] all lie on a straight line
|
||||
# if point[k] is to the left of point[i] or it's to the
|
||||
# right of point[j], then point[i], point[j] cannot be
|
||||
# part of the convex hull of A
|
||||
elif points[k] < points[i] or points[k] > points[j]:
|
||||
ij_part_of_convex_hull = False
|
||||
break
|
||||
|
||||
if points_left_of_ij and points_right_of_ij:
|
||||
ij_part_of_convex_hull = False
|
||||
|
|
|
@ -2,6 +2,20 @@ def actual_power(a: int, b: int):
|
|||
"""
|
||||
Function using divide and conquer to calculate a^b.
|
||||
It only works for integer a,b.
|
||||
|
||||
:param a: The base of the power operation, an integer.
|
||||
:param b: The exponent of the power operation, a non-negative integer.
|
||||
:return: The result of a^b.
|
||||
|
||||
Examples:
|
||||
>>> actual_power(3, 2)
|
||||
9
|
||||
>>> actual_power(5, 3)
|
||||
125
|
||||
>>> actual_power(2, 5)
|
||||
32
|
||||
>>> actual_power(7, 0)
|
||||
1
|
||||
"""
|
||||
if b == 0:
|
||||
return 1
|
||||
|
@ -13,6 +27,10 @@ def actual_power(a: int, b: int):
|
|||
|
||||
def power(a: int, b: int) -> float:
|
||||
"""
|
||||
:param a: The base (integer).
|
||||
:param b: The exponent (integer).
|
||||
:return: The result of a^b, as a float for negative exponents.
|
||||
|
||||
>>> power(4,6)
|
||||
4096
|
||||
>>> power(2,3)
|
||||
|
|
0
docs/__init__.py
Normal file
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
|
||||
of chosen elements is “tar”. For every element, we have two choices
|
||||
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))
|
||||
return (0, 1)
|
||||
|
||||
# F(2n) = F(n)[2F(n+1) − F(n)]
|
||||
# F(2n) = F(n)[2F(n+1) - F(n)]
|
||||
# F(2n+1) = F(n+1)^2+F(n)^2
|
||||
a, b = _fib(n // 2)
|
||||
c = a * (b * 2 - a)
|
||||
|
|
|
@ -12,19 +12,58 @@ class Graph:
|
|||
] # dp[i][j] stores minimum distance from i to j
|
||||
|
||||
def add_edge(self, u, v, w):
|
||||
"""
|
||||
Adds a directed edge from node u
|
||||
to node v with weight w.
|
||||
|
||||
>>> g = Graph(3)
|
||||
>>> g.add_edge(0, 1, 5)
|
||||
>>> g.dp[0][1]
|
||||
5
|
||||
"""
|
||||
self.dp[u][v] = w
|
||||
|
||||
def floyd_warshall(self):
|
||||
"""
|
||||
Computes the shortest paths between all pairs of
|
||||
nodes using the Floyd-Warshall algorithm.
|
||||
|
||||
>>> g = Graph(3)
|
||||
>>> g.add_edge(0, 1, 1)
|
||||
>>> g.add_edge(1, 2, 2)
|
||||
>>> g.floyd_warshall()
|
||||
>>> g.show_min(0, 2)
|
||||
3
|
||||
>>> g.show_min(2, 0)
|
||||
inf
|
||||
"""
|
||||
for k in range(self.n):
|
||||
for i in range(self.n):
|
||||
for j in range(self.n):
|
||||
self.dp[i][j] = min(self.dp[i][j], self.dp[i][k] + self.dp[k][j])
|
||||
|
||||
def show_min(self, u, v):
|
||||
"""
|
||||
Returns the minimum distance from node u to node v.
|
||||
|
||||
>>> g = Graph(3)
|
||||
>>> g.add_edge(0, 1, 3)
|
||||
>>> g.add_edge(1, 2, 4)
|
||||
>>> g.floyd_warshall()
|
||||
>>> g.show_min(0, 2)
|
||||
7
|
||||
>>> g.show_min(1, 0)
|
||||
inf
|
||||
"""
|
||||
return self.dp[u][v]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
# Example usage
|
||||
graph = Graph(5)
|
||||
graph.add_edge(0, 2, 9)
|
||||
graph.add_edge(0, 4, 10)
|
||||
|
@ -38,5 +77,9 @@ if __name__ == "__main__":
|
|||
graph.add_edge(4, 2, 4)
|
||||
graph.add_edge(4, 3, 9)
|
||||
graph.floyd_warshall()
|
||||
graph.show_min(1, 4)
|
||||
graph.show_min(0, 3)
|
||||
print(
|
||||
graph.show_min(1, 4)
|
||||
) # Should output the minimum distance from node 1 to node 4
|
||||
print(
|
||||
graph.show_min(0, 3)
|
||||
) # Should output the minimum distance from node 0 to node 3
|
||||
|
|
|
@ -28,6 +28,24 @@ def longest_common_subsequence(x: str, y: str):
|
|||
(2, 'ph')
|
||||
>>> longest_common_subsequence("computer", "food")
|
||||
(1, 'o')
|
||||
>>> longest_common_subsequence("", "abc") # One string is empty
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("abc", "") # Other string is empty
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("", "") # Both strings are empty
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("abc", "def") # No common subsequence
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("abc", "abc") # Identical strings
|
||||
(3, 'abc')
|
||||
>>> longest_common_subsequence("a", "a") # Single character match
|
||||
(1, 'a')
|
||||
>>> longest_common_subsequence("a", "b") # Single character no match
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("abcdef", "ace") # Interleaved subsequence
|
||||
(3, 'ace')
|
||||
>>> longest_common_subsequence("ABCD", "ACBD") # No repeated characters
|
||||
(3, 'ABD')
|
||||
"""
|
||||
# find the length of strings
|
||||
|
||||
|
@ -38,30 +56,30 @@ def longest_common_subsequence(x: str, y: str):
|
|||
n = len(y)
|
||||
|
||||
# declaring the array for storing the dp values
|
||||
l = [[0] * (n + 1) for _ in range(m + 1)] # noqa: E741
|
||||
dp = [[0] * (n + 1) for _ in range(m + 1)]
|
||||
|
||||
for i in range(1, m + 1):
|
||||
for j in range(1, n + 1):
|
||||
match = 1 if x[i - 1] == y[j - 1] else 0
|
||||
|
||||
l[i][j] = max(l[i - 1][j], l[i][j - 1], l[i - 1][j - 1] + match)
|
||||
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1] + match)
|
||||
|
||||
seq = ""
|
||||
i, j = m, n
|
||||
while i > 0 and j > 0:
|
||||
match = 1 if x[i - 1] == y[j - 1] else 0
|
||||
|
||||
if l[i][j] == l[i - 1][j - 1] + match:
|
||||
if dp[i][j] == dp[i - 1][j - 1] + match:
|
||||
if match == 1:
|
||||
seq = x[i - 1] + seq
|
||||
i -= 1
|
||||
j -= 1
|
||||
elif l[i][j] == l[i - 1][j]:
|
||||
elif dp[i][j] == dp[i - 1][j]:
|
||||
i -= 1
|
||||
else:
|
||||
j -= 1
|
||||
|
||||
return l[m][n], seq
|
||||
return dp[m][n], seq
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
def ceil_index(v, l, r, key): # noqa: E741
|
||||
while r - l > 1:
|
||||
m = (l + r) // 2
|
||||
if v[m] >= key:
|
||||
r = m
|
||||
def ceil_index(v, left, right, key):
|
||||
while right - left > 1:
|
||||
middle = (left + right) // 2
|
||||
if v[middle] >= key:
|
||||
right = middle
|
||||
else:
|
||||
l = m # noqa: E741
|
||||
return r
|
||||
left = middle
|
||||
return right
|
||||
|
||||
|
||||
def longest_increasing_subsequence_length(v: list[int]) -> int:
|
|
@ -45,7 +45,7 @@ def subset_combinations(elements: list[int], n: int) -> list:
|
|||
for i in range(1, r + 1):
|
||||
for j in range(i, 0, -1):
|
||||
for prev_combination in dp[j - 1]:
|
||||
dp[j].append(tuple(prev_combination) + (elements[i - 1],))
|
||||
dp[j].append((*prev_combination, elements[i - 1]))
|
||||
|
||||
try:
|
||||
return sorted(dp[n])
|
||||
|
|
0
electronics/__init__.py
Normal file
0
electronics/__init__.py
Normal file
|
@ -37,10 +37,9 @@ class CircularConvolution:
|
|||
using matrix method
|
||||
|
||||
Usage:
|
||||
>>> import circular_convolution as cc
|
||||
>>> convolution = cc.CircularConvolution()
|
||||
>>> convolution = CircularConvolution()
|
||||
>>> convolution.circular_convolution()
|
||||
[10, 10, 6, 14]
|
||||
[10.0, 10.0, 6.0, 14.0]
|
||||
|
||||
>>> convolution.first_signal = [0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6]
|
||||
>>> convolution.second_signal = [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3, 1.5]
|
||||
|
@ -55,7 +54,7 @@ class CircularConvolution:
|
|||
>>> convolution.first_signal = [1, -1, 2, 3, -1]
|
||||
>>> convolution.second_signal = [1, 2, 3]
|
||||
>>> convolution.circular_convolution()
|
||||
[8, -2, 3, 4, 11]
|
||||
[8.0, -2.0, 3.0, 4.0, 11.0]
|
||||
|
||||
"""
|
||||
|
||||
|
@ -92,7 +91,7 @@ class CircularConvolution:
|
|||
final_signal = np.matmul(np.transpose(matrix), np.transpose(self.first_signal))
|
||||
|
||||
# rounding-off to two decimal places
|
||||
return [round(i, 2) for i in final_signal]
|
||||
return [float(round(i, 2)) for i in final_signal]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -20,8 +20,8 @@ def couloumbs_law(
|
|||
|
||||
Reference
|
||||
----------
|
||||
Coulomb (1785) "Premier mémoire sur l’électricité et le magnétisme,"
|
||||
Histoire de l’Académie Royale des Sciences, pp. 569–577.
|
||||
Coulomb (1785) "Premier mémoire sur l'électricité et le magnétisme,"
|
||||
Histoire de l'Académie Royale des Sciences, pp. 569-577.
|
||||
|
||||
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/)
|
||||
* 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 typing import Any
|
||||
|
||||
import numpy
|
||||
from matplotlib import pyplot
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
c_cauliflower = 0.25 + 0.0j
|
||||
c_polynomial_1 = -0.4 + 0.6j
|
||||
|
@ -37,22 +37,20 @@ window_size = 2.0
|
|||
nb_pixels = 666
|
||||
|
||||
|
||||
def eval_exponential(c_parameter: complex, z_values: numpy.ndarray) -> numpy.ndarray:
|
||||
def eval_exponential(c_parameter: complex, z_values: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Evaluate $e^z + c$.
|
||||
>>> eval_exponential(0, 0)
|
||||
>>> float(eval_exponential(0, 0))
|
||||
1.0
|
||||
>>> abs(eval_exponential(1, numpy.pi*1.j)) < 1e-15
|
||||
>>> bool(abs(eval_exponential(1, np.pi*1.j)) < 1e-15)
|
||||
True
|
||||
>>> abs(eval_exponential(1.j, 0)-1-1.j) < 1e-15
|
||||
>>> bool(abs(eval_exponential(1.j, 0)-1-1.j) < 1e-15)
|
||||
True
|
||||
"""
|
||||
return numpy.exp(z_values) + c_parameter
|
||||
return np.exp(z_values) + c_parameter
|
||||
|
||||
|
||||
def eval_quadratic_polynomial(
|
||||
c_parameter: complex, z_values: numpy.ndarray
|
||||
) -> numpy.ndarray:
|
||||
def eval_quadratic_polynomial(c_parameter: complex, z_values: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
>>> eval_quadratic_polynomial(0, 2)
|
||||
4
|
||||
|
@ -66,7 +64,7 @@ def eval_quadratic_polynomial(
|
|||
return z_values * z_values + c_parameter
|
||||
|
||||
|
||||
def prepare_grid(window_size: float, nb_pixels: int) -> numpy.ndarray:
|
||||
def prepare_grid(window_size: float, nb_pixels: int) -> np.ndarray:
|
||||
"""
|
||||
Create a grid of complex values of size nb_pixels*nb_pixels with real and
|
||||
imaginary parts ranging from -window_size to window_size (inclusive).
|
||||
|
@ -77,20 +75,20 @@ def prepare_grid(window_size: float, nb_pixels: int) -> numpy.ndarray:
|
|||
[ 0.-1.j, 0.+0.j, 0.+1.j],
|
||||
[ 1.-1.j, 1.+0.j, 1.+1.j]])
|
||||
"""
|
||||
x = numpy.linspace(-window_size, window_size, nb_pixels)
|
||||
x = np.linspace(-window_size, window_size, nb_pixels)
|
||||
x = x.reshape((nb_pixels, 1))
|
||||
y = numpy.linspace(-window_size, window_size, nb_pixels)
|
||||
y = np.linspace(-window_size, window_size, nb_pixels)
|
||||
y = y.reshape((1, nb_pixels))
|
||||
return x + 1.0j * y
|
||||
|
||||
|
||||
def iterate_function(
|
||||
eval_function: Callable[[Any, numpy.ndarray], numpy.ndarray],
|
||||
eval_function: Callable[[Any, np.ndarray], np.ndarray],
|
||||
function_params: Any,
|
||||
nb_iterations: int,
|
||||
z_0: numpy.ndarray,
|
||||
z_0: np.ndarray,
|
||||
infinity: float | None = None,
|
||||
) -> numpy.ndarray:
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Iterate the function "eval_function" exactly nb_iterations times.
|
||||
The first argument of the function is a parameter which is contained in
|
||||
|
@ -98,22 +96,22 @@ def iterate_function(
|
|||
values to iterate from.
|
||||
This function returns the final iterates.
|
||||
|
||||
>>> iterate_function(eval_quadratic_polynomial, 0, 3, numpy.array([0,1,2])).shape
|
||||
>>> iterate_function(eval_quadratic_polynomial, 0, 3, np.array([0,1,2])).shape
|
||||
(3,)
|
||||
>>> numpy.round(iterate_function(eval_quadratic_polynomial,
|
||||
>>> complex(np.round(iterate_function(eval_quadratic_polynomial,
|
||||
... 0,
|
||||
... 3,
|
||||
... numpy.array([0,1,2]))[0])
|
||||
... np.array([0,1,2]))[0]))
|
||||
0j
|
||||
>>> numpy.round(iterate_function(eval_quadratic_polynomial,
|
||||
>>> complex(np.round(iterate_function(eval_quadratic_polynomial,
|
||||
... 0,
|
||||
... 3,
|
||||
... numpy.array([0,1,2]))[1])
|
||||
... np.array([0,1,2]))[1]))
|
||||
(1+0j)
|
||||
>>> numpy.round(iterate_function(eval_quadratic_polynomial,
|
||||
>>> complex(np.round(iterate_function(eval_quadratic_polynomial,
|
||||
... 0,
|
||||
... 3,
|
||||
... numpy.array([0,1,2]))[2])
|
||||
... np.array([0,1,2]))[2]))
|
||||
(256+0j)
|
||||
"""
|
||||
|
||||
|
@ -121,8 +119,8 @@ def iterate_function(
|
|||
for _ in range(nb_iterations):
|
||||
z_n = eval_function(function_params, z_n)
|
||||
if infinity is not None:
|
||||
numpy.nan_to_num(z_n, copy=False, nan=infinity)
|
||||
z_n[abs(z_n) == numpy.inf] = infinity
|
||||
np.nan_to_num(z_n, copy=False, nan=infinity)
|
||||
z_n[abs(z_n) == np.inf] = infinity
|
||||
return z_n
|
||||
|
||||
|
||||
|
@ -130,21 +128,21 @@ def show_results(
|
|||
function_label: str,
|
||||
function_params: Any,
|
||||
escape_radius: float,
|
||||
z_final: numpy.ndarray,
|
||||
z_final: np.ndarray,
|
||||
) -> None:
|
||||
"""
|
||||
Plots of whether the absolute value of z_final is greater than
|
||||
the value of escape_radius. Adds the function_label and function_params to
|
||||
the title.
|
||||
|
||||
>>> show_results('80', 0, 1, numpy.array([[0,1,.5],[.4,2,1.1],[.2,1,1.3]]))
|
||||
>>> show_results('80', 0, 1, np.array([[0,1,.5],[.4,2,1.1],[.2,1,1.3]]))
|
||||
"""
|
||||
|
||||
abs_z_final = (abs(z_final)).transpose()
|
||||
abs_z_final[:, :] = abs_z_final[::-1, :]
|
||||
pyplot.matshow(abs_z_final < escape_radius)
|
||||
pyplot.title(f"Julia set of ${function_label}$, $c={function_params}$")
|
||||
pyplot.show()
|
||||
plt.matshow(abs_z_final < escape_radius)
|
||||
plt.title(f"Julia set of ${function_label}$, $c={function_params}$")
|
||||
plt.show()
|
||||
|
||||
|
||||
def ignore_overflow_warnings() -> None:
|
||||
|
|
|
@ -22,25 +22,25 @@ Requirements (pip):
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import matplotlib.pyplot as plt # type: ignore
|
||||
import numpy
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# initial triangle of Koch snowflake
|
||||
VECTOR_1 = numpy.array([0, 0])
|
||||
VECTOR_2 = numpy.array([0.5, 0.8660254])
|
||||
VECTOR_3 = numpy.array([1, 0])
|
||||
VECTOR_1 = np.array([0, 0])
|
||||
VECTOR_2 = np.array([0.5, 0.8660254])
|
||||
VECTOR_3 = np.array([1, 0])
|
||||
INITIAL_VECTORS = [VECTOR_1, VECTOR_2, VECTOR_3, VECTOR_1]
|
||||
|
||||
# uncomment for simple Koch curve instead of Koch snowflake
|
||||
# INITIAL_VECTORS = [VECTOR_1, VECTOR_3]
|
||||
|
||||
|
||||
def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndarray]:
|
||||
def iterate(initial_vectors: list[np.ndarray], steps: int) -> list[np.ndarray]:
|
||||
"""
|
||||
Go through the number of iterations determined by the argument "steps".
|
||||
Be careful with high values (above 5) since the time to calculate increases
|
||||
exponentially.
|
||||
>>> iterate([numpy.array([0, 0]), numpy.array([1, 0])], 1)
|
||||
>>> iterate([np.array([0, 0]), np.array([1, 0])], 1)
|
||||
[array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \
|
||||
0.28867513]), array([0.66666667, 0. ]), array([1, 0])]
|
||||
"""
|
||||
|
@ -50,13 +50,13 @@ def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndar
|
|||
return vectors
|
||||
|
||||
|
||||
def iteration_step(vectors: list[numpy.ndarray]) -> list[numpy.ndarray]:
|
||||
def iteration_step(vectors: list[np.ndarray]) -> list[np.ndarray]:
|
||||
"""
|
||||
Loops through each pair of adjacent vectors. Each line between two adjacent
|
||||
vectors is divided into 4 segments by adding 3 additional vectors in-between
|
||||
the original two vectors. The vector in the middle is constructed through a
|
||||
60 degree rotation so it is bent outwards.
|
||||
>>> iteration_step([numpy.array([0, 0]), numpy.array([1, 0])])
|
||||
>>> iteration_step([np.array([0, 0]), np.array([1, 0])])
|
||||
[array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \
|
||||
0.28867513]), array([0.66666667, 0. ]), array([1, 0])]
|
||||
"""
|
||||
|
@ -74,22 +74,22 @@ def iteration_step(vectors: list[numpy.ndarray]) -> list[numpy.ndarray]:
|
|||
return new_vectors
|
||||
|
||||
|
||||
def rotate(vector: numpy.ndarray, angle_in_degrees: float) -> numpy.ndarray:
|
||||
def rotate(vector: np.ndarray, angle_in_degrees: float) -> np.ndarray:
|
||||
"""
|
||||
Standard rotation of a 2D vector with a rotation matrix
|
||||
(see https://en.wikipedia.org/wiki/Rotation_matrix )
|
||||
>>> rotate(numpy.array([1, 0]), 60)
|
||||
>>> rotate(np.array([1, 0]), 60)
|
||||
array([0.5 , 0.8660254])
|
||||
>>> rotate(numpy.array([1, 0]), 90)
|
||||
>>> rotate(np.array([1, 0]), 90)
|
||||
array([6.123234e-17, 1.000000e+00])
|
||||
"""
|
||||
theta = numpy.radians(angle_in_degrees)
|
||||
c, s = numpy.cos(theta), numpy.sin(theta)
|
||||
rotation_matrix = numpy.array(((c, -s), (s, c)))
|
||||
return numpy.dot(rotation_matrix, vector)
|
||||
theta = np.radians(angle_in_degrees)
|
||||
c, s = np.cos(theta), np.sin(theta)
|
||||
rotation_matrix = np.array(((c, -s), (s, c)))
|
||||
return np.dot(rotation_matrix, vector)
|
||||
|
||||
|
||||
def plot(vectors: list[numpy.ndarray]) -> None:
|
||||
def plot(vectors: list[np.ndarray]) -> None:
|
||||
"""
|
||||
Utility function to plot the vectors using matplotlib.pyplot
|
||||
No doctest was implemented since this function does not have a return value
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user