mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-01-30 14:13:44 +00:00
Merge branch 'TheAlgorithms:master' into game_of_life_tests
This commit is contained in:
commit
f03d77ade5
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
|
@ -10,21 +10,18 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: uv.lock
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.13
|
||||
allow-prereleases: true
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
python -m pip install pytest-cov -r requirements.txt
|
||||
- run: uv sync --group=test
|
||||
- name: Run tests
|
||||
# TODO: #8818 Re-enable quantum tests
|
||||
run: pytest
|
||||
run: uv run pytest
|
||||
--ignore=computer_vision/cnn_classification.py
|
||||
--ignore=docs/conf.py
|
||||
--ignore=dynamic_programming/k_means_clustering_tensorflow.py
|
||||
|
|
16
.github/workflows/project_euler.yml
vendored
16
.github/workflows/project_euler.yml
vendored
|
@ -15,25 +15,21 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- name: Install pytest and pytest-cov
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade numpy pytest pytest-cov
|
||||
- run: pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/
|
||||
- run: uv sync --group=euler-validate --group=test
|
||||
- run: uv run pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/
|
||||
validate-solutions:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- name: Install pytest and requests
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade numpy pytest requests
|
||||
- run: pytest scripts/validate_solutions.py
|
||||
- run: uv sync --group=euler-validate --group=test
|
||||
- run: uv run pytest scripts/validate_solutions.py
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
4
.github/workflows/ruff.yml
vendored
4
.github/workflows/ruff.yml
vendored
|
@ -12,5 +12,5 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install --user ruff
|
||||
- run: ruff check --output-format=github .
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- run: uvx ruff check --output-format=github .
|
||||
|
|
6
.github/workflows/sphinx.yml
vendored
6
.github/workflows/sphinx.yml
vendored
|
@ -26,14 +26,14 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- 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
|
||||
- run: uv sync --group=docs
|
||||
- uses: actions/configure-pages@v5
|
||||
- run: sphinx-build -c docs . docs/_build/html
|
||||
- run: uv run sphinx-build -c docs . docs/_build/html
|
||||
- uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/_build/html
|
||||
|
|
|
@ -16,7 +16,7 @@ repos:
|
|||
- id: auto-walrus
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.7.0
|
||||
rev: v0.9.1
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
|
@ -29,7 +29,7 @@ repos:
|
|||
- tomli
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "2.4.3"
|
||||
rev: "v2.5.0"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
|
@ -42,12 +42,12 @@ repos:
|
|||
pass_filenames: false
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.21
|
||||
rev: v0.23
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.12.1
|
||||
rev: v1.14.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
args:
|
||||
|
|
11
DIRECTORY.md
11
DIRECTORY.md
|
@ -86,7 +86,7 @@
|
|||
* [Baconian Cipher](ciphers/baconian_cipher.py)
|
||||
* [Base16](ciphers/base16.py)
|
||||
* [Base32](ciphers/base32.py)
|
||||
* [Base64](ciphers/base64.py)
|
||||
* [Base64 Cipher](ciphers/base64_cipher.py)
|
||||
* [Base85](ciphers/base85.py)
|
||||
* [Beaufort Cipher](ciphers/beaufort_cipher.py)
|
||||
* [Bifid](ciphers/bifid.py)
|
||||
|
@ -142,6 +142,7 @@
|
|||
* [Haralick Descriptors](computer_vision/haralick_descriptors.py)
|
||||
* [Harris Corner](computer_vision/harris_corner.py)
|
||||
* [Horn Schunck](computer_vision/horn_schunck.py)
|
||||
* [Intensity Based Segmentation](computer_vision/intensity_based_segmentation.py)
|
||||
* [Mean Threshold](computer_vision/mean_threshold.py)
|
||||
* [Mosaic Augmentation](computer_vision/mosaic_augmentation.py)
|
||||
* [Pooling Functions](computer_vision/pooling_functions.py)
|
||||
|
@ -169,6 +170,7 @@
|
|||
* [Prefix Conversions](conversions/prefix_conversions.py)
|
||||
* [Prefix Conversions String](conversions/prefix_conversions_string.py)
|
||||
* [Pressure Conversions](conversions/pressure_conversions.py)
|
||||
* [Rectangular To Polar](conversions/rectangular_to_polar.py)
|
||||
* [Rgb Cmyk Conversion](conversions/rgb_cmyk_conversion.py)
|
||||
* [Rgb Hsv Conversion](conversions/rgb_hsv_conversion.py)
|
||||
* [Roman Numerals](conversions/roman_numerals.py)
|
||||
|
@ -460,6 +462,7 @@
|
|||
|
||||
## Graphics
|
||||
* [Bezier Curve](graphics/bezier_curve.py)
|
||||
* [Digital Differential Analyzer Line](graphics/digital_differential_analyzer_line.py)
|
||||
* [Vector3 For 2D Rendering](graphics/vector3_for_2d_rendering.py)
|
||||
|
||||
## Graphs
|
||||
|
@ -506,6 +509,7 @@
|
|||
* [Kahns Algorithm Long](graphs/kahns_algorithm_long.py)
|
||||
* [Kahns Algorithm Topo](graphs/kahns_algorithm_topo.py)
|
||||
* [Karger](graphs/karger.py)
|
||||
* [Lanczos Eigenvectors](graphs/lanczos_eigenvectors.py)
|
||||
* [Markov Chain](graphs/markov_chain.py)
|
||||
* [Matching Min Vertex Cover](graphs/matching_min_vertex_cover.py)
|
||||
* [Minimum Path Sum](graphs/minimum_path_sum.py)
|
||||
|
@ -660,6 +664,7 @@
|
|||
* [Gamma](maths/gamma.py)
|
||||
* [Gaussian](maths/gaussian.py)
|
||||
* [Gcd Of N Numbers](maths/gcd_of_n_numbers.py)
|
||||
* [Geometric Mean](maths/geometric_mean.py)
|
||||
* [Germain Primes](maths/germain_primes.py)
|
||||
* [Greatest Common Divisor](maths/greatest_common_divisor.py)
|
||||
* [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py)
|
||||
|
@ -794,6 +799,7 @@
|
|||
* [Cramers Rule 2X2](matrix/cramers_rule_2x2.py)
|
||||
* [Inverse Of Matrix](matrix/inverse_of_matrix.py)
|
||||
* [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py)
|
||||
* [Matrix Based Game](matrix/matrix_based_game.py)
|
||||
* [Matrix Class](matrix/matrix_class.py)
|
||||
* [Matrix Equalization](matrix/matrix_equalization.py)
|
||||
* [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py)
|
||||
|
@ -884,6 +890,7 @@
|
|||
* [N Body Simulation](physics/n_body_simulation.py)
|
||||
* [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py)
|
||||
* [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py)
|
||||
* [Period Of Pendulum](physics/period_of_pendulum.py)
|
||||
* [Photoelectric Effect](physics/photoelectric_effect.py)
|
||||
* [Potential Energy](physics/potential_energy.py)
|
||||
* [Rainfall Intensity](physics/rainfall_intensity.py)
|
||||
|
@ -1324,7 +1331,7 @@
|
|||
* [Title](strings/title.py)
|
||||
* [Top K Frequent Words](strings/top_k_frequent_words.py)
|
||||
* [Upper](strings/upper.py)
|
||||
* [Wave](strings/wave.py)
|
||||
* [Wave String](strings/wave_string.py)
|
||||
* [Wildcard Pattern Matching](strings/wildcard_pattern_matching.py)
|
||||
* [Word Occurrence](strings/word_occurrence.py)
|
||||
* [Word Patterns](strings/word_patterns.py)
|
||||
|
|
|
@ -10,13 +10,17 @@ class IIRFilter:
|
|||
|
||||
Implementation details:
|
||||
Based on the 2nd-order function from
|
||||
https://en.wikipedia.org/wiki/Digital_biquad_filter,
|
||||
https://en.wikipedia.org/wiki/Digital_biquad_filter,
|
||||
this generalized N-order function was made.
|
||||
|
||||
Using the following transfer function
|
||||
H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}}
|
||||
.. math:: H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}
|
||||
{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}}
|
||||
|
||||
we can rewrite this to
|
||||
y[n]={\frac{1}{a_{0}}}\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right)
|
||||
.. math:: y[n]={\frac{1}{a_{0}}}
|
||||
\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-
|
||||
\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right)
|
||||
"""
|
||||
|
||||
def __init__(self, order: int) -> None:
|
||||
|
@ -34,17 +38,19 @@ class IIRFilter:
|
|||
|
||||
def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None:
|
||||
"""
|
||||
Set the coefficients for the IIR filter. These should both be of size order + 1.
|
||||
a_0 may be left out, and it will use 1.0 as default value.
|
||||
Set the coefficients for the IIR filter.
|
||||
These should both be of size `order` + 1.
|
||||
:math:`a_0` may be left out, and it will use 1.0 as default value.
|
||||
|
||||
This method works well with scipy's filter design functions
|
||||
>>> # Make a 2nd-order 1000Hz butterworth lowpass filter
|
||||
>>> import scipy.signal
|
||||
>>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000,
|
||||
... btype='lowpass',
|
||||
... fs=48000)
|
||||
>>> filt = IIRFilter(2)
|
||||
>>> filt.set_coefficients(a_coeffs, b_coeffs)
|
||||
|
||||
>>> # Make a 2nd-order 1000Hz butterworth lowpass filter
|
||||
>>> import scipy.signal
|
||||
>>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000,
|
||||
... btype='lowpass',
|
||||
... fs=48000)
|
||||
>>> filt = IIRFilter(2)
|
||||
>>> filt.set_coefficients(a_coeffs, b_coeffs)
|
||||
"""
|
||||
if len(a_coeffs) < self.order:
|
||||
a_coeffs = [1.0, *a_coeffs]
|
||||
|
@ -68,7 +74,7 @@ class IIRFilter:
|
|||
|
||||
def process(self, sample: float) -> float:
|
||||
"""
|
||||
Calculate y[n]
|
||||
Calculate :math:`y[n]`
|
||||
|
||||
>>> filt = IIRFilter(2)
|
||||
>>> filt.process(0)
|
||||
|
|
|
@ -12,6 +12,8 @@ from itertools import combinations
|
|||
|
||||
def combination_lists(n: int, k: int) -> list[list[int]]:
|
||||
"""
|
||||
Generates all possible combinations of k numbers out of 1 ... n using itertools.
|
||||
|
||||
>>> combination_lists(n=4, k=2)
|
||||
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
|
||||
"""
|
||||
|
@ -20,6 +22,8 @@ def combination_lists(n: int, k: int) -> list[list[int]]:
|
|||
|
||||
def generate_all_combinations(n: int, k: int) -> list[list[int]]:
|
||||
"""
|
||||
Generates all possible combinations of k numbers out of 1 ... n using backtracking.
|
||||
|
||||
>>> generate_all_combinations(n=4, k=2)
|
||||
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
|
||||
>>> generate_all_combinations(n=0, k=0)
|
||||
|
@ -34,6 +38,14 @@ def generate_all_combinations(n: int, k: int) -> list[list[int]]:
|
|||
ValueError: n must not be negative
|
||||
>>> generate_all_combinations(n=5, k=4)
|
||||
[[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]]
|
||||
>>> generate_all_combinations(n=3, k=3)
|
||||
[[1, 2, 3]]
|
||||
>>> generate_all_combinations(n=3, k=1)
|
||||
[[1], [2], [3]]
|
||||
>>> generate_all_combinations(n=1, k=0)
|
||||
[[]]
|
||||
>>> generate_all_combinations(n=1, k=1)
|
||||
[[1]]
|
||||
>>> from itertools import combinations
|
||||
>>> all(generate_all_combinations(n, k) == combination_lists(n, k)
|
||||
... for n in range(1, 6) for k in range(1, 6))
|
||||
|
@ -56,6 +68,28 @@ def create_all_state(
|
|||
current_list: list[int],
|
||||
total_list: list[list[int]],
|
||||
) -> None:
|
||||
"""
|
||||
Helper function to recursively build all combinations.
|
||||
|
||||
>>> create_all_state(1, 4, 2, [], result := [])
|
||||
>>> result
|
||||
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
|
||||
>>> create_all_state(1, 3, 3, [], result := [])
|
||||
>>> result
|
||||
[[1, 2, 3]]
|
||||
>>> create_all_state(2, 2, 1, [1], result := [])
|
||||
>>> result
|
||||
[[1, 2]]
|
||||
>>> create_all_state(1, 0, 0, [], result := [])
|
||||
>>> result
|
||||
[[]]
|
||||
>>> create_all_state(1, 4, 0, [1, 2], result := [])
|
||||
>>> result
|
||||
[[1, 2]]
|
||||
>>> create_all_state(5, 4, 2, [1, 2], result := [])
|
||||
>>> result
|
||||
[]
|
||||
"""
|
||||
if level == 0:
|
||||
total_list.append(current_list[:])
|
||||
return
|
||||
|
|
|
@ -58,10 +58,8 @@ def new_generation(cells: list[list[int]]) -> list[list[int]]:
|
|||
# 3. All other live cells die in the next generation.
|
||||
# Similarly, all other dead cells stay dead.
|
||||
alive = cells[i][j] == 1
|
||||
if (
|
||||
(alive and 2 <= neighbour_count <= 3)
|
||||
or not alive
|
||||
and neighbour_count == 3
|
||||
if (alive and 2 <= neighbour_count <= 3) or (
|
||||
not alive and neighbour_count == 3
|
||||
):
|
||||
next_generation_row.append(1)
|
||||
else:
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"""
|
||||
Wa-Tor algorithm (1984)
|
||||
|
||||
@ https://en.wikipedia.org/wiki/Wa-Tor
|
||||
@ https://beltoforion.de/en/wator/
|
||||
@ https://beltoforion.de/en/wator/images/wator_medium.webm
|
||||
| @ https://en.wikipedia.org/wiki/Wa-Tor
|
||||
| @ https://beltoforion.de/en/wator/
|
||||
| @ https://beltoforion.de/en/wator/images/wator_medium.webm
|
||||
|
||||
This solution aims to completely remove any systematic approach
|
||||
to the Wa-Tor planet, and utilise fully random methods.
|
||||
|
@ -97,8 +97,8 @@ class WaTor:
|
|||
|
||||
:attr time_passed: A function that is called every time
|
||||
time passes (a chronon) in order to visually display
|
||||
the new Wa-Tor planet. The time_passed function can block
|
||||
using time.sleep to slow the algorithm progression.
|
||||
the new Wa-Tor planet. The `time_passed` function can block
|
||||
using ``time.sleep`` to slow the algorithm progression.
|
||||
|
||||
>>> wt = WaTor(10, 15)
|
||||
>>> wt.width
|
||||
|
@ -216,7 +216,7 @@ class WaTor:
|
|||
"""
|
||||
Returns all the prey entities around (N, S, E, W) a predator entity.
|
||||
|
||||
Subtly different to the try_to_move_to_unoccupied square.
|
||||
Subtly different to the `move_and_reproduce`.
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> wt.set_planet([
|
||||
|
@ -260,7 +260,7 @@ class WaTor:
|
|||
"""
|
||||
Attempts to move to an unoccupied neighbouring square
|
||||
in either of the four directions (North, South, East, West).
|
||||
If the move was successful and the remaining_reproduction time is
|
||||
If the move was successful and the `remaining_reproduction_time` is
|
||||
equal to 0, then a new prey or predator can also be created
|
||||
in the previous square.
|
||||
|
||||
|
@ -351,12 +351,12 @@ class WaTor:
|
|||
Performs the actions for a prey entity
|
||||
|
||||
For prey the rules are:
|
||||
1. At each chronon, a prey moves randomly to one of the adjacent unoccupied
|
||||
squares. If there are no free squares, no movement takes place.
|
||||
2. Once a prey has survived a certain number of chronons it may reproduce.
|
||||
This is done as it moves to a neighbouring square,
|
||||
leaving behind a new prey in its old position.
|
||||
Its reproduction time is also reset to zero.
|
||||
1. At each chronon, a prey moves randomly to one of the adjacent unoccupied
|
||||
squares. If there are no free squares, no movement takes place.
|
||||
2. Once a prey has survived a certain number of chronons it may reproduce.
|
||||
This is done as it moves to a neighbouring square,
|
||||
leaving behind a new prey in its old position.
|
||||
Its reproduction time is also reset to zero.
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> reproducable_entity = Entity(True, coords=(0, 1))
|
||||
|
@ -382,15 +382,15 @@ class WaTor:
|
|||
:param occupied_by_prey_coords: Move to this location if there is prey there
|
||||
|
||||
For predators the rules are:
|
||||
1. At each chronon, a predator moves randomly to an adjacent square occupied
|
||||
by a prey. If there is none, the predator moves to a random adjacent
|
||||
unoccupied square. If there are no free squares, no movement takes place.
|
||||
2. At each chronon, each predator is deprived of a unit of energy.
|
||||
3. Upon reaching zero energy, a predator dies.
|
||||
4. If a predator moves to a square occupied by a prey,
|
||||
it eats the prey and earns a certain amount of energy.
|
||||
5. Once a predator has survived a certain number of chronons
|
||||
it may reproduce in exactly the same way as the prey.
|
||||
1. At each chronon, a predator moves randomly to an adjacent square occupied
|
||||
by a prey. If there is none, the predator moves to a random adjacent
|
||||
unoccupied square. If there are no free squares, no movement takes place.
|
||||
2. At each chronon, each predator is deprived of a unit of energy.
|
||||
3. Upon reaching zero energy, a predator dies.
|
||||
4. If a predator moves to a square occupied by a prey,
|
||||
it eats the prey and earns a certain amount of energy.
|
||||
5. Once a predator has survived a certain number of chronons
|
||||
it may reproduce in exactly the same way as the prey.
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> wt.set_planet([[Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1))]])
|
||||
|
@ -430,7 +430,7 @@ class WaTor:
|
|||
|
||||
def run(self, *, iteration_count: int) -> None:
|
||||
"""
|
||||
Emulate time passing by looping iteration_count times
|
||||
Emulate time passing by looping `iteration_count` times
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> wt.run(iteration_count=PREDATOR_INITIAL_ENERGY_VALUE - 1)
|
||||
|
@ -484,11 +484,9 @@ def visualise(wt: WaTor, iter_number: int, *, colour: bool = True) -> None:
|
|||
an ascii code in terminal to clear and re-print
|
||||
the Wa-Tor planet at intervals.
|
||||
|
||||
Uses ascii colour codes to colourfully display
|
||||
the predators and prey.
|
||||
|
||||
(0x60f197) Prey = #
|
||||
(0xfffff) Predator = x
|
||||
Uses ascii colour codes to colourfully display the predators and prey:
|
||||
* (0x60f197) Prey = ``#``
|
||||
* (0xfffff) Predator = ``x``
|
||||
|
||||
>>> wt = WaTor(30, 30)
|
||||
>>> wt.set_planet([
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""
|
||||
https://en.wikipedia.org/wiki/Autokey_cipher
|
||||
|
||||
An autokey cipher (also known as the autoclave cipher) is a cipher that
|
||||
incorporates the message (the plaintext) into the key.
|
||||
The key is generated from the message in some automated fashion,
|
||||
|
@ -10,8 +11,9 @@ by adding a short primer key to the front of the message.
|
|||
|
||||
def encrypt(plaintext: str, key: str) -> str:
|
||||
"""
|
||||
Encrypt a given plaintext (string) and key (string), returning the
|
||||
Encrypt a given `plaintext` (string) and `key` (string), returning the
|
||||
encrypted ciphertext.
|
||||
|
||||
>>> encrypt("hello world", "coffee")
|
||||
'jsqqs avvwo'
|
||||
>>> encrypt("coffee is good as python", "TheAlgorithms")
|
||||
|
@ -74,8 +76,9 @@ def encrypt(plaintext: str, key: str) -> str:
|
|||
|
||||
def decrypt(ciphertext: str, key: str) -> str:
|
||||
"""
|
||||
Decrypt a given ciphertext (string) and key (string), returning the decrypted
|
||||
Decrypt a given `ciphertext` (string) and `key` (string), returning the decrypted
|
||||
ciphertext.
|
||||
|
||||
>>> decrypt("jsqqs avvwo", "coffee")
|
||||
'hello world'
|
||||
>>> decrypt("vvjfpk wj ohvp su ddylsv", "TheAlgorithms")
|
||||
|
|
|
@ -105,13 +105,13 @@ def base64_decode(encoded_data: str) -> bytes:
|
|||
|
||||
# Check if the encoded string contains non base64 characters
|
||||
if padding:
|
||||
assert all(
|
||||
char in B64_CHARSET for char in encoded_data[:-padding]
|
||||
), "Invalid base64 character(s) found."
|
||||
assert all(char in B64_CHARSET for char in encoded_data[:-padding]), (
|
||||
"Invalid base64 character(s) found."
|
||||
)
|
||||
else:
|
||||
assert all(
|
||||
char in B64_CHARSET for char in encoded_data
|
||||
), "Invalid base64 character(s) found."
|
||||
assert all(char in B64_CHARSET for char in encoded_data), (
|
||||
"Invalid base64 character(s) found."
|
||||
)
|
||||
|
||||
# Check the padding
|
||||
assert len(encoded_data) % 4 == 0 and padding < 3, "Incorrect padding"
|
|
@ -7,24 +7,29 @@ def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
|
|||
"""
|
||||
encrypt
|
||||
=======
|
||||
|
||||
Encodes a given string with the caesar cipher and returns the encoded
|
||||
message
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
* input_string: the plain-text that needs to be encoded
|
||||
* key: the number of letters to shift the message by
|
||||
|
||||
* `input_string`: the plain-text that needs to be encoded
|
||||
* `key`: the number of letters to shift the message by
|
||||
|
||||
Optional:
|
||||
* alphabet (None): the alphabet used to encode the cipher, if not
|
||||
|
||||
* `alphabet` (``None``): the alphabet used to encode the cipher, if not
|
||||
specified, the standard english alphabet with upper and lowercase
|
||||
letters is used
|
||||
|
||||
Returns:
|
||||
|
||||
* A string containing the encoded cipher-text
|
||||
|
||||
More on the caesar cipher
|
||||
=========================
|
||||
|
||||
The caesar cipher is named after Julius Caesar who used it when sending
|
||||
secret military messages to his troops. This is a simple substitution cipher
|
||||
where every character in the plain-text is shifted by a certain number known
|
||||
|
@ -32,26 +37,28 @@ def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
|
|||
|
||||
Example:
|
||||
Say we have the following message:
|
||||
"Hello, captain"
|
||||
``Hello, captain``
|
||||
|
||||
And our alphabet is made up of lower and uppercase letters:
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
``abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ``
|
||||
|
||||
And our shift is "2"
|
||||
And our shift is ``2``
|
||||
|
||||
We can then encode the message, one letter at a time. "H" would become "J",
|
||||
since "J" is two letters away, and so on. If the shift is ever two large, or
|
||||
We can then encode the message, one letter at a time. ``H`` would become ``J``,
|
||||
since ``J`` is two letters away, and so on. If the shift is ever two large, or
|
||||
our letter is at the end of the alphabet, we just start at the beginning
|
||||
("Z" would shift to "a" then "b" and so on).
|
||||
(``Z`` would shift to ``a`` then ``b`` and so on).
|
||||
|
||||
Our final message would be "Jgnnq, ecrvckp"
|
||||
Our final message would be ``Jgnnq, ecrvckp``
|
||||
|
||||
Further reading
|
||||
===============
|
||||
|
||||
* https://en.m.wikipedia.org/wiki/Caesar_cipher
|
||||
|
||||
Doctests
|
||||
========
|
||||
|
||||
>>> encrypt('The quick brown fox jumps over the lazy dog', 8)
|
||||
'bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo'
|
||||
|
||||
|
@ -85,23 +92,28 @@ def decrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
|
|||
"""
|
||||
decrypt
|
||||
=======
|
||||
|
||||
Decodes a given string of cipher-text and returns the decoded plain-text
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
* input_string: the cipher-text that needs to be decoded
|
||||
* key: the number of letters to shift the message backwards by to decode
|
||||
|
||||
* `input_string`: the cipher-text that needs to be decoded
|
||||
* `key`: the number of letters to shift the message backwards by to decode
|
||||
|
||||
Optional:
|
||||
* alphabet (None): the alphabet used to decode the cipher, if not
|
||||
|
||||
* `alphabet` (``None``): the alphabet used to decode the cipher, if not
|
||||
specified, the standard english alphabet with upper and lowercase
|
||||
letters is used
|
||||
|
||||
Returns:
|
||||
|
||||
* A string containing the decoded plain-text
|
||||
|
||||
More on the caesar cipher
|
||||
=========================
|
||||
|
||||
The caesar cipher is named after Julius Caesar who used it when sending
|
||||
secret military messages to his troops. This is a simple substitution cipher
|
||||
where very character in the plain-text is shifted by a certain number known
|
||||
|
@ -110,27 +122,29 @@ def decrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
|
|||
|
||||
Example:
|
||||
Say we have the following cipher-text:
|
||||
"Jgnnq, ecrvckp"
|
||||
``Jgnnq, ecrvckp``
|
||||
|
||||
And our alphabet is made up of lower and uppercase letters:
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
``abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ``
|
||||
|
||||
And our shift is "2"
|
||||
And our shift is ``2``
|
||||
|
||||
To decode the message, we would do the same thing as encoding, but in
|
||||
reverse. The first letter, "J" would become "H" (remember: we are decoding)
|
||||
because "H" is two letters in reverse (to the left) of "J". We would
|
||||
continue doing this. A letter like "a" would shift back to the end of
|
||||
the alphabet, and would become "Z" or "Y" and so on.
|
||||
reverse. The first letter, ``J`` would become ``H`` (remember: we are decoding)
|
||||
because ``H`` is two letters in reverse (to the left) of ``J``. We would
|
||||
continue doing this. A letter like ``a`` would shift back to the end of
|
||||
the alphabet, and would become ``Z`` or ``Y`` and so on.
|
||||
|
||||
Our final message would be "Hello, captain"
|
||||
Our final message would be ``Hello, captain``
|
||||
|
||||
Further reading
|
||||
===============
|
||||
|
||||
* https://en.m.wikipedia.org/wiki/Caesar_cipher
|
||||
|
||||
Doctests
|
||||
========
|
||||
|
||||
>>> decrypt('bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo', 8)
|
||||
'The quick brown fox jumps over the lazy dog'
|
||||
|
||||
|
@ -150,41 +164,44 @@ def brute_force(input_string: str, alphabet: str | None = None) -> dict[int, str
|
|||
"""
|
||||
brute_force
|
||||
===========
|
||||
|
||||
Returns all the possible combinations of keys and the decoded strings in the
|
||||
form of a dictionary
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
* input_string: the cipher-text that needs to be used during brute-force
|
||||
|
||||
* `input_string`: the cipher-text that needs to be used during brute-force
|
||||
|
||||
Optional:
|
||||
* alphabet: (None): the alphabet used to decode the cipher, if not
|
||||
|
||||
* `alphabet` (``None``): the alphabet used to decode the cipher, if not
|
||||
specified, the standard english alphabet with upper and lowercase
|
||||
letters is used
|
||||
|
||||
More about brute force
|
||||
======================
|
||||
|
||||
Brute force is when a person intercepts a message or password, not knowing
|
||||
the key and tries every single combination. This is easy with the caesar
|
||||
cipher since there are only all the letters in the alphabet. The more
|
||||
complex the cipher, the larger amount of time it will take to do brute force
|
||||
|
||||
Ex:
|
||||
Say we have a 5 letter alphabet (abcde), for simplicity and we intercepted the
|
||||
following message:
|
||||
|
||||
"dbc"
|
||||
|
||||
Say we have a ``5`` letter alphabet (``abcde``), for simplicity and we intercepted
|
||||
the following message: ``dbc``,
|
||||
we could then just write out every combination:
|
||||
ecd... and so on, until we reach a combination that makes sense:
|
||||
"cab"
|
||||
``ecd``... and so on, until we reach a combination that makes sense:
|
||||
``cab``
|
||||
|
||||
Further reading
|
||||
===============
|
||||
|
||||
* https://en.wikipedia.org/wiki/Brute_force
|
||||
|
||||
Doctests
|
||||
========
|
||||
|
||||
>>> brute_force("jFyuMy xIH'N vLONy zILwy Gy!")[20]
|
||||
"Please don't brute force me!"
|
||||
|
||||
|
@ -208,7 +225,7 @@ def brute_force(input_string: str, alphabet: str | None = None) -> dict[int, str
|
|||
|
||||
if __name__ == "__main__":
|
||||
while True:
|
||||
print(f'\n{"-" * 10}\n Menu\n{"-" * 10}')
|
||||
print(f"\n{'-' * 10}\n Menu\n{'-' * 10}")
|
||||
print(*["1.Encrypt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n")
|
||||
|
||||
# get user input
|
||||
|
|
|
@ -11,33 +11,31 @@ def decrypt_caesar_with_chi_squared(
|
|||
"""
|
||||
Basic Usage
|
||||
===========
|
||||
|
||||
Arguments:
|
||||
* ciphertext (str): the text to decode (encoded with the caesar cipher)
|
||||
* `ciphertext` (str): the text to decode (encoded with the caesar cipher)
|
||||
|
||||
Optional Arguments:
|
||||
* cipher_alphabet (list): the alphabet used for the cipher (each letter is
|
||||
a string separated by commas)
|
||||
* frequencies_dict (dict): a dictionary of word frequencies where keys are
|
||||
the letters and values are a percentage representation of the frequency as
|
||||
a decimal/float
|
||||
* case_sensitive (bool): a boolean value: True if the case matters during
|
||||
decryption, False if it doesn't
|
||||
* `cipher_alphabet` (list): the alphabet used for the cipher (each letter is
|
||||
a string separated by commas)
|
||||
* `frequencies_dict` (dict): a dictionary of word frequencies where keys are
|
||||
the letters and values are a percentage representation of the frequency as
|
||||
a decimal/float
|
||||
* `case_sensitive` (bool): a boolean value: ``True`` if the case matters during
|
||||
decryption, ``False`` if it doesn't
|
||||
|
||||
Returns:
|
||||
* A tuple in the form of:
|
||||
(
|
||||
most_likely_cipher,
|
||||
most_likely_cipher_chi_squared_value,
|
||||
decoded_most_likely_cipher
|
||||
)
|
||||
* A tuple in the form of:
|
||||
(`most_likely_cipher`, `most_likely_cipher_chi_squared_value`,
|
||||
`decoded_most_likely_cipher`)
|
||||
|
||||
where...
|
||||
- most_likely_cipher is an integer representing the shift of the smallest
|
||||
chi-squared statistic (most likely key)
|
||||
- most_likely_cipher_chi_squared_value is a float representing the
|
||||
chi-squared statistic of the most likely shift
|
||||
- decoded_most_likely_cipher is a string with the decoded cipher
|
||||
(decoded by the most_likely_cipher key)
|
||||
where...
|
||||
- `most_likely_cipher` is an integer representing the shift of the smallest
|
||||
chi-squared statistic (most likely key)
|
||||
- `most_likely_cipher_chi_squared_value` is a float representing the
|
||||
chi-squared statistic of the most likely shift
|
||||
- `decoded_most_likely_cipher` is a string with the decoded cipher
|
||||
(decoded by the most_likely_cipher key)
|
||||
|
||||
|
||||
The Chi-squared test
|
||||
|
@ -45,52 +43,57 @@ def decrypt_caesar_with_chi_squared(
|
|||
|
||||
The caesar cipher
|
||||
-----------------
|
||||
|
||||
The caesar cipher is a very insecure encryption algorithm, however it has
|
||||
been used since Julius Caesar. The cipher is a simple substitution cipher
|
||||
where each character in the plain text is replaced by a character in the
|
||||
alphabet a certain number of characters after the original character. The
|
||||
number of characters away is called the shift or key. For example:
|
||||
|
||||
Plain text: hello
|
||||
Key: 1
|
||||
Cipher text: ifmmp
|
||||
(each letter in hello has been shifted one to the right in the eng. alphabet)
|
||||
| Plain text: ``hello``
|
||||
| Key: ``1``
|
||||
| Cipher text: ``ifmmp``
|
||||
| (each letter in ``hello`` has been shifted one to the right in the eng. alphabet)
|
||||
|
||||
As you can imagine, this doesn't provide lots of security. In fact
|
||||
decrypting ciphertext by brute-force is extremely easy even by hand. However
|
||||
one way to do that is the chi-squared test.
|
||||
one way to do that is the chi-squared test.
|
||||
|
||||
The chi-squared test
|
||||
-------------------
|
||||
--------------------
|
||||
|
||||
Each letter in the english alphabet has a frequency, or the amount of times
|
||||
it shows up compared to other letters (usually expressed as a decimal
|
||||
representing the percentage likelihood). The most common letter in the
|
||||
english language is "e" with a frequency of 0.11162 or 11.162%. The test is
|
||||
completed in the following fashion.
|
||||
english language is ``e`` with a frequency of ``0.11162`` or ``11.162%``.
|
||||
The test is completed in the following fashion.
|
||||
|
||||
1. The ciphertext is decoded in a brute force way (every combination of the
|
||||
26 possible combinations)
|
||||
``26`` possible combinations)
|
||||
2. For every combination, for each letter in the combination, the average
|
||||
amount of times the letter should appear the message is calculated by
|
||||
multiplying the total number of characters by the frequency of the letter
|
||||
multiplying the total number of characters by the frequency of the letter.
|
||||
|
||||
For example:
|
||||
In a message of 100 characters, e should appear around 11.162 times.
|
||||
| For example:
|
||||
| In a message of ``100`` characters, ``e`` should appear around ``11.162``
|
||||
times.
|
||||
|
||||
3. Then, to calculate the margin of error (the amount of times the letter
|
||||
SHOULD appear with the amount of times the letter DOES appear), we use
|
||||
the chi-squared test. The following formula is used:
|
||||
3. Then, to calculate the margin of error (the amount of times the letter
|
||||
SHOULD appear with the amount of times the letter DOES appear), we use
|
||||
the chi-squared test. The following formula is used:
|
||||
|
||||
Let:
|
||||
- n be the number of times the letter actually appears
|
||||
- p be the predicted value of the number of times the letter should
|
||||
appear (see #2)
|
||||
- let v be the chi-squared test result (referred to here as chi-squared
|
||||
value/statistic)
|
||||
Let:
|
||||
- n be the number of times the letter actually appears
|
||||
- p be the predicted value of the number of times the letter should
|
||||
appear (see item ``2``)
|
||||
- let v be the chi-squared test result (referred to here as chi-squared
|
||||
value/statistic)
|
||||
|
||||
(n - p)^2
|
||||
--------- = v
|
||||
p
|
||||
::
|
||||
|
||||
(n - p)^2
|
||||
--------- = v
|
||||
p
|
||||
|
||||
4. Each chi squared value for each letter is then added up to the total.
|
||||
The total is the chi-squared statistic for that encryption key.
|
||||
|
@ -98,16 +101,16 @@ def decrypt_caesar_with_chi_squared(
|
|||
to be the decoded answer.
|
||||
|
||||
Further Reading
|
||||
================
|
||||
===============
|
||||
|
||||
* http://practicalcryptography.com/cryptanalysis/text-characterisation/chi-squared-
|
||||
statistic/
|
||||
* http://practicalcryptography.com/cryptanalysis/text-characterisation/chi-squared-statistic/
|
||||
* https://en.wikipedia.org/wiki/Letter_frequency
|
||||
* https://en.wikipedia.org/wiki/Chi-squared_test
|
||||
* https://en.m.wikipedia.org/wiki/Caesar_cipher
|
||||
|
||||
Doctests
|
||||
========
|
||||
|
||||
>>> decrypt_caesar_with_chi_squared(
|
||||
... 'dof pz aol jhlzhy jpwoly zv wvwbshy? pa pz avv lhzf av jyhjr!'
|
||||
... ) # doctest: +NORMALIZE_WHITESPACE
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
"""
|
||||
Wikipedia: https://en.wikipedia.org/wiki/Enigma_machine
|
||||
Video explanation: https://youtu.be/QwQVMqfoB2E
|
||||
Also check out Numberphile's and Computerphile's videos on this topic
|
||||
| Wikipedia: https://en.wikipedia.org/wiki/Enigma_machine
|
||||
| Video explanation: https://youtu.be/QwQVMqfoB2E
|
||||
| Also check out Numberphile's and Computerphile's videos on this topic
|
||||
|
||||
This module contains function 'enigma' which emulates
|
||||
This module contains function ``enigma`` which emulates
|
||||
the famous Enigma machine from WWII.
|
||||
|
||||
Module includes:
|
||||
- enigma function
|
||||
|
||||
- ``enigma`` function
|
||||
- showcase of function usage
|
||||
- 9 randomly generated rotors
|
||||
- ``9`` randomly generated rotors
|
||||
- reflector (aka static rotor)
|
||||
- original alphabet
|
||||
|
||||
|
@ -73,7 +75,7 @@ def _validator(
|
|||
rotpos: RotorPositionT, rotsel: RotorSelectionT, pb: str
|
||||
) -> tuple[RotorPositionT, RotorSelectionT, dict[str, str]]:
|
||||
"""
|
||||
Checks if the values can be used for the 'enigma' function
|
||||
Checks if the values can be used for the ``enigma`` function
|
||||
|
||||
>>> _validator((1,1,1), (rotor1, rotor2, rotor3), 'POLAND')
|
||||
((1, 1, 1), ('EGZWVONAHDCLFQMSIPJBYUKXTR', 'FOBHMDKEXQNRAULPGSJVTYICZW', \
|
||||
|
@ -83,7 +85,7 @@ def _validator(
|
|||
:param rotpos: rotor_positon
|
||||
:param rotsel: rotor_selection
|
||||
:param pb: plugb -> validated and transformed
|
||||
:return: (rotpos, rotsel, pb)
|
||||
:return: (`rotpos`, `rotsel`, `pb`)
|
||||
"""
|
||||
# Checks if there are 3 unique rotors
|
||||
|
||||
|
@ -118,9 +120,10 @@ def _plugboard(pbstring: str) -> dict[str, str]:
|
|||
>>> _plugboard('POLAND')
|
||||
{'P': 'O', 'O': 'P', 'L': 'A', 'A': 'L', 'N': 'D', 'D': 'N'}
|
||||
|
||||
In the code, 'pb' stands for 'plugboard'
|
||||
In the code, ``pb`` stands for ``plugboard``
|
||||
|
||||
Pairs can be separated by spaces
|
||||
|
||||
:param pbstring: string containing plugboard setting for the Enigma machine
|
||||
:return: dictionary containing converted pairs
|
||||
"""
|
||||
|
@ -168,31 +171,34 @@ def enigma(
|
|||
plugb: str = "",
|
||||
) -> str:
|
||||
"""
|
||||
The only difference with real-world enigma is that I allowed string input.
|
||||
The only difference with real-world enigma is that ``I`` allowed string input.
|
||||
All characters are converted to uppercase. (non-letter symbol are ignored)
|
||||
How it works:
|
||||
(for every letter in the message)
|
||||
|
||||
| How it works:
|
||||
| (for every letter in the message)
|
||||
|
||||
- Input letter goes into the plugboard.
|
||||
If it is connected to another one, switch it.
|
||||
If it is connected to another one, switch it.
|
||||
|
||||
- Letter goes through 3 rotors.
|
||||
Each rotor can be represented as 2 sets of symbol, where one is shuffled.
|
||||
Each symbol from the first set has corresponding symbol in
|
||||
the second set and vice versa.
|
||||
- Letter goes through ``3`` rotors.
|
||||
Each rotor can be represented as ``2`` sets of symbol, where one is shuffled.
|
||||
Each symbol from the first set has corresponding symbol in
|
||||
the second set and vice versa.
|
||||
|
||||
example:
|
||||
| ABCDEFGHIJKLMNOPQRSTUVWXYZ | e.g. F=D and D=F
|
||||
| VKLEPDBGRNWTFCJOHQAMUZYIXS |
|
||||
example::
|
||||
|
||||
| ABCDEFGHIJKLMNOPQRSTUVWXYZ | e.g. F=D and D=F
|
||||
| VKLEPDBGRNWTFCJOHQAMUZYIXS |
|
||||
|
||||
- Symbol then goes through reflector (static rotor).
|
||||
There it is switched with paired symbol
|
||||
The reflector can be represented as2 sets, each with half of the alphanet.
|
||||
There are usually 10 pairs of letters.
|
||||
There it is switched with paired symbol.
|
||||
The reflector can be represented as ``2`` sets, each with half of the alphanet.
|
||||
There are usually ``10`` pairs of letters.
|
||||
|
||||
Example:
|
||||
| ABCDEFGHIJKLM | e.g. E is paired to X
|
||||
| ZYXWVUTSRQPON | so when E goes in X goes out and vice versa
|
||||
Example::
|
||||
|
||||
| ABCDEFGHIJKLM | e.g. E is paired to X
|
||||
| ZYXWVUTSRQPON | so when E goes in X goes out and vice versa
|
||||
|
||||
- Letter then goes through the rotors again
|
||||
|
||||
|
@ -211,9 +217,9 @@ def enigma(
|
|||
|
||||
|
||||
:param text: input message
|
||||
:param rotor_position: tuple with 3 values in range 1..26
|
||||
:param rotor_selection: tuple with 3 rotors ()
|
||||
:param plugb: string containing plugboard configuration (default '')
|
||||
:param rotor_position: tuple with ``3`` values in range ``1``.. ``26``
|
||||
:param rotor_selection: tuple with ``3`` rotors
|
||||
:param plugb: string containing plugboard configuration (default ``''``)
|
||||
:return: en/decrypted string
|
||||
"""
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import string
|
|||
from collections.abc import Generator, Iterable
|
||||
|
||||
|
||||
def chunker(seq: Iterable[str], size: int) -> Generator[tuple[str, ...], None, None]:
|
||||
def chunker(seq: Iterable[str], size: int) -> Generator[tuple[str, ...]]:
|
||||
it = iter(seq)
|
||||
while True:
|
||||
chunk = tuple(itertools.islice(it, size))
|
||||
|
|
|
@ -3,8 +3,10 @@ An RSA prime factor algorithm.
|
|||
|
||||
The program can efficiently factor RSA prime number given the private key d and
|
||||
public key e.
|
||||
Source: on page 3 of https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf
|
||||
More readable source: https://www.di-mgt.com.au/rsa_factorize_n.html
|
||||
|
||||
| Source: on page ``3`` of https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf
|
||||
| More readable source: https://www.di-mgt.com.au/rsa_factorize_n.html
|
||||
|
||||
large number can take minutes to factor, therefore are not included in doctest.
|
||||
"""
|
||||
|
||||
|
@ -17,13 +19,14 @@ import random
|
|||
def rsafactor(d: int, e: int, n: int) -> list[int]:
|
||||
"""
|
||||
This function returns the factors of N, where p*q=N
|
||||
Return: [p, q]
|
||||
|
||||
Return: [p, q]
|
||||
|
||||
We call N the RSA modulus, e the encryption exponent, and d the decryption exponent.
|
||||
The pair (N, e) is the public key. As its name suggests, it is public and is used to
|
||||
encrypt messages.
|
||||
encrypt messages.
|
||||
The pair (N, d) is the secret key or private key and is known only to the recipient
|
||||
of encrypted messages.
|
||||
of encrypted messages.
|
||||
|
||||
>>> rsafactor(3, 16971, 25777)
|
||||
[149, 173]
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
def remove_duplicates(key: str) -> str:
|
||||
"""
|
||||
Removes duplicate alphabetic characters in a keyword (letter is ignored after its
|
||||
first appearance).
|
||||
first appearance).
|
||||
|
||||
:param key: Keyword to use
|
||||
:return: String with duplicates removed
|
||||
|
||||
>>> remove_duplicates('Hello World!!')
|
||||
'Helo Wrd'
|
||||
"""
|
||||
|
||||
key_no_dups = ""
|
||||
for ch in key:
|
||||
if ch == " " or ch not in key_no_dups and ch.isalpha():
|
||||
if ch == " " or (ch not in key_no_dups and ch.isalpha()):
|
||||
key_no_dups += ch
|
||||
return key_no_dups
|
||||
|
||||
|
@ -18,6 +20,7 @@ def remove_duplicates(key: str) -> str:
|
|||
def create_cipher_map(key: str) -> dict[str, str]:
|
||||
"""
|
||||
Returns a cipher map given a keyword.
|
||||
|
||||
:param key: keyword to use
|
||||
:return: dictionary cipher map
|
||||
"""
|
||||
|
@ -43,9 +46,11 @@ def create_cipher_map(key: str) -> dict[str, str]:
|
|||
def encipher(message: str, cipher_map: dict[str, str]) -> str:
|
||||
"""
|
||||
Enciphers a message given a cipher map.
|
||||
|
||||
:param message: Message to encipher
|
||||
:param cipher_map: Cipher map
|
||||
:return: enciphered string
|
||||
|
||||
>>> encipher('Hello World!!', create_cipher_map('Goodbye!!'))
|
||||
'CYJJM VMQJB!!'
|
||||
"""
|
||||
|
@ -55,9 +60,11 @@ def encipher(message: str, cipher_map: dict[str, str]) -> str:
|
|||
def decipher(message: str, cipher_map: dict[str, str]) -> str:
|
||||
"""
|
||||
Deciphers a message given a cipher map
|
||||
|
||||
:param message: Message to decipher
|
||||
:param cipher_map: Dictionary mapping to use
|
||||
:return: Deciphered string
|
||||
|
||||
>>> cipher_map = create_cipher_map('Goodbye!!')
|
||||
>>> decipher(encipher('Hello World!!', cipher_map), cipher_map)
|
||||
'HELLO WORLD!!'
|
||||
|
@ -70,6 +77,7 @@ def decipher(message: str, cipher_map: dict[str, str]) -> str:
|
|||
def main() -> None:
|
||||
"""
|
||||
Handles I/O
|
||||
|
||||
:return: void
|
||||
"""
|
||||
message = input("Enter message to encode or decode: ").strip()
|
||||
|
|
|
@ -52,10 +52,8 @@ def decrypt_message(key: int, message: str) -> str:
|
|||
plain_text[col] += symbol
|
||||
col += 1
|
||||
|
||||
if (
|
||||
(col == num_cols)
|
||||
or (col == num_cols - 1)
|
||||
and (row >= num_rows - num_shaded_boxes)
|
||||
if (col == num_cols) or (
|
||||
(col == num_cols - 1) and (row >= num_rows - num_shaded_boxes)
|
||||
):
|
||||
col = 0
|
||||
row += 1
|
||||
|
|
|
@ -22,7 +22,7 @@ TEST_NUMBER_TO_CHARACTER = {val: key for key, val in TEST_CHARACTER_TO_NUMBER.it
|
|||
|
||||
def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str:
|
||||
"""
|
||||
Arrange the triagram value of each letter of 'message_part' vertically and join
|
||||
Arrange the triagram value of each letter of `message_part` vertically and join
|
||||
them horizontally.
|
||||
|
||||
>>> __encrypt_part('ASK', TEST_CHARACTER_TO_NUMBER)
|
||||
|
@ -65,8 +65,8 @@ def __prepare(
|
|||
"""
|
||||
A helper function that generates the triagrams and assigns each letter of the
|
||||
alphabet to its corresponding triagram and stores this in a dictionary
|
||||
("character_to_number" and "number_to_character") after confirming if the
|
||||
alphabet's length is 27.
|
||||
(`character_to_number` and `number_to_character`) after confirming if the
|
||||
alphabet's length is ``27``.
|
||||
|
||||
>>> test = __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxYZ+')
|
||||
>>> expected = ('IAMABOY','ABCDEFGHIJKLMNOPQRSTUVWXYZ+',
|
||||
|
@ -75,24 +75,28 @@ def __prepare(
|
|||
True
|
||||
|
||||
Testing with incomplete alphabet
|
||||
|
||||
>>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVw')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'Length of alphabet has to be 27.'
|
||||
|
||||
Testing with extra long alphabets
|
||||
|
||||
>>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxyzzwwtyyujjgfd')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'Length of alphabet has to be 27.'
|
||||
|
||||
Testing with punctuations that are not in the given alphabet
|
||||
|
||||
>>> __prepare('am i a boy?','abCdeFghijkLmnopqrStuVwxYZ+')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Each message character has to be included in alphabet!
|
||||
|
||||
Testing with numbers
|
||||
|
||||
>>> __prepare(500,'abCdeFghijkLmnopqrStuVwxYZ+')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -130,9 +134,9 @@ def encrypt_message(
|
|||
PARAMETERS
|
||||
----------
|
||||
|
||||
* message: The message you want to encrypt.
|
||||
* alphabet (optional): The characters to be used for the cipher .
|
||||
* period (optional): The number of characters you want in a group whilst
|
||||
* `message`: The message you want to encrypt.
|
||||
* `alphabet` (optional): The characters to be used for the cipher .
|
||||
* `period` (optional): The number of characters you want in a group whilst
|
||||
encrypting.
|
||||
|
||||
>>> encrypt_message('I am a boy')
|
||||
|
@ -169,20 +173,21 @@ def decrypt_message(
|
|||
decrypt_message
|
||||
===============
|
||||
|
||||
Decrypts a trifid_cipher encrypted message .
|
||||
Decrypts a trifid_cipher encrypted message.
|
||||
|
||||
PARAMETERS
|
||||
----------
|
||||
|
||||
* message: The message you want to decrypt .
|
||||
* alphabet (optional): The characters used for the cipher.
|
||||
* period (optional): The number of characters used in grouping when it
|
||||
* `message`: The message you want to decrypt.
|
||||
* `alphabet` (optional): The characters used for the cipher.
|
||||
* `period` (optional): The number of characters used in grouping when it
|
||||
was encrypted.
|
||||
|
||||
>>> decrypt_message('BCDGBQY')
|
||||
'IAMABOY'
|
||||
|
||||
Decrypting with your own alphabet and period
|
||||
|
||||
>>> decrypt_message('FMJFVOISSUFTFPUFEQQC','FELIXMARDSTBCGHJKNOPQUVWYZ+',5)
|
||||
'AIDETOILECIELTAIDERA'
|
||||
"""
|
||||
|
|
|
@ -35,8 +35,8 @@ def add_key_to_lexicon(
|
|||
lexicon[curr_string + "0"] = last_match_id
|
||||
|
||||
if math.log2(index).is_integer():
|
||||
for curr_key in lexicon:
|
||||
lexicon[curr_key] = "0" + lexicon[curr_key]
|
||||
for curr_key, value in lexicon.items():
|
||||
lexicon[curr_key] = f"0{value}"
|
||||
|
||||
lexicon[curr_string + "1"] = bin(index)[2:]
|
||||
|
||||
|
|
|
@ -8,4 +8,3 @@ Image processing and computer vision are a little different from each other. Ima
|
|||
While computer vision comes from modelling image processing using the techniques of machine learning, computer vision applies machine learning to recognize patterns for interpretation of images (much like the process of visual reasoning of human vision).
|
||||
|
||||
* <https://en.wikipedia.org/wiki/Computer_vision>
|
||||
* <https://www.datarobot.com/blog/introduction-to-computer-vision-what-it-is-and-how-it-works/>
|
||||
|
|
|
@ -33,7 +33,7 @@ def main() -> None:
|
|||
file_name = paths[index].split(os.sep)[-1].rsplit(".", 1)[0]
|
||||
file_root = f"{OUTPUT_DIR}/{file_name}_FLIP_{letter_code}"
|
||||
cv2.imwrite(f"{file_root}.jpg", image, [cv2.IMWRITE_JPEG_QUALITY, 85])
|
||||
print(f"Success {index+1}/{len(new_images)} with {file_name}")
|
||||
print(f"Success {index + 1}/{len(new_images)} with {file_name}")
|
||||
annos_list = []
|
||||
for anno in new_annos[index]:
|
||||
obj = f"{anno[0]} {anno[1]} {anno[2]} {anno[3]} {anno[4]}"
|
||||
|
|
62
computer_vision/intensity_based_segmentation.py
Normal file
62
computer_vision/intensity_based_segmentation.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Source: "https://www.ijcse.com/docs/IJCSE11-02-03-117.pdf"
|
||||
|
||||
# Importing necessary libraries
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def segment_image(image: np.ndarray, thresholds: list[int]) -> np.ndarray:
|
||||
"""
|
||||
Performs image segmentation based on intensity thresholds.
|
||||
|
||||
Args:
|
||||
image: Input grayscale image as a 2D array.
|
||||
thresholds: Intensity thresholds to define segments.
|
||||
|
||||
Returns:
|
||||
A labeled 2D array where each region corresponds to a threshold range.
|
||||
|
||||
Example:
|
||||
>>> img = np.array([[80, 120, 180], [40, 90, 150], [20, 60, 100]])
|
||||
>>> segment_image(img, [50, 100, 150])
|
||||
array([[1, 2, 3],
|
||||
[0, 1, 2],
|
||||
[0, 1, 1]], dtype=int32)
|
||||
"""
|
||||
# Initialize segmented array with zeros
|
||||
segmented = np.zeros_like(image, dtype=np.int32)
|
||||
|
||||
# Assign labels based on thresholds
|
||||
for i, threshold in enumerate(thresholds):
|
||||
segmented[image > threshold] = i + 1
|
||||
|
||||
return segmented
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Load the image
|
||||
image_path = "path_to_image" # Replace with your image path
|
||||
original_image = Image.open(image_path).convert("L")
|
||||
image_array = np.array(original_image)
|
||||
|
||||
# Define thresholds
|
||||
thresholds = [50, 100, 150, 200]
|
||||
|
||||
# Perform segmentation
|
||||
segmented_image = segment_image(image_array, thresholds)
|
||||
|
||||
# Display the results
|
||||
plt.figure(figsize=(10, 5))
|
||||
|
||||
plt.subplot(1, 2, 1)
|
||||
plt.title("Original Image")
|
||||
plt.imshow(image_array, cmap="gray")
|
||||
plt.axis("off")
|
||||
|
||||
plt.subplot(1, 2, 2)
|
||||
plt.title("Segmented Image")
|
||||
plt.imshow(segmented_image, cmap="tab20")
|
||||
plt.axis("off")
|
||||
|
||||
plt.show()
|
|
@ -41,7 +41,7 @@ def main() -> None:
|
|||
file_name = path.split(os.sep)[-1].rsplit(".", 1)[0]
|
||||
file_root = f"{OUTPUT_DIR}/{file_name}_MOSAIC_{letter_code}"
|
||||
cv2.imwrite(f"{file_root}.jpg", new_image, [cv2.IMWRITE_JPEG_QUALITY, 85])
|
||||
print(f"Succeeded {index+1}/{NUMBER_IMAGES} with {file_name}")
|
||||
print(f"Succeeded {index + 1}/{NUMBER_IMAGES} with {file_name}")
|
||||
annos_list = []
|
||||
for anno in new_annos:
|
||||
width = anno[3] - anno[1]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from enum import Enum
|
||||
from typing import ClassVar, Literal
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class NumberingSystem(Enum):
|
||||
|
@ -54,7 +54,7 @@ class NumberingSystem(Enum):
|
|||
|
||||
|
||||
class NumberWords(Enum):
|
||||
ONES: ClassVar[dict[int, str]] = {
|
||||
ONES = { # noqa: RUF012
|
||||
0: "",
|
||||
1: "one",
|
||||
2: "two",
|
||||
|
@ -67,7 +67,7 @@ class NumberWords(Enum):
|
|||
9: "nine",
|
||||
}
|
||||
|
||||
TEENS: ClassVar[dict[int, str]] = {
|
||||
TEENS = { # noqa: RUF012
|
||||
0: "ten",
|
||||
1: "eleven",
|
||||
2: "twelve",
|
||||
|
@ -80,7 +80,7 @@ class NumberWords(Enum):
|
|||
9: "nineteen",
|
||||
}
|
||||
|
||||
TENS: ClassVar[dict[int, str]] = {
|
||||
TENS = { # noqa: RUF012
|
||||
2: "twenty",
|
||||
3: "thirty",
|
||||
4: "forty",
|
||||
|
|
32
conversions/rectangular_to_polar.py
Normal file
32
conversions/rectangular_to_polar.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
import math
|
||||
|
||||
|
||||
def rectangular_to_polar(real: float, img: float) -> tuple[float, float]:
|
||||
"""
|
||||
https://en.wikipedia.org/wiki/Polar_coordinate_system
|
||||
|
||||
>>> rectangular_to_polar(5,-5)
|
||||
(7.07, -45.0)
|
||||
>>> rectangular_to_polar(-1,1)
|
||||
(1.41, 135.0)
|
||||
>>> rectangular_to_polar(-1,-1)
|
||||
(1.41, -135.0)
|
||||
>>> rectangular_to_polar(1e-10,1e-10)
|
||||
(0.0, 45.0)
|
||||
>>> rectangular_to_polar(-1e-10,1e-10)
|
||||
(0.0, 135.0)
|
||||
>>> rectangular_to_polar(9.75,5.93)
|
||||
(11.41, 31.31)
|
||||
>>> rectangular_to_polar(10000,99999)
|
||||
(100497.76, 84.29)
|
||||
"""
|
||||
|
||||
mod = round(math.sqrt((real**2) + (img**2)), 2)
|
||||
ang = round(math.degrees(math.atan2(img, real)), 2)
|
||||
return (mod, ang)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -9,6 +9,16 @@ def is_monotonic(nums: list[int]) -> bool:
|
|||
True
|
||||
>>> is_monotonic([1, 3, 2])
|
||||
False
|
||||
>>> is_monotonic([1,2,3,4,5,6,5])
|
||||
False
|
||||
>>> is_monotonic([-3,-2,-1])
|
||||
True
|
||||
>>> is_monotonic([-5,-6,-7])
|
||||
True
|
||||
>>> is_monotonic([0,0,0])
|
||||
True
|
||||
>>> is_monotonic([-100,0,100])
|
||||
True
|
||||
"""
|
||||
return all(nums[i] <= nums[i + 1] for i in range(len(nums) - 1)) or all(
|
||||
nums[i] >= nums[i + 1] for i in range(len(nums) - 1)
|
||||
|
@ -21,3 +31,7 @@ if __name__ == "__main__":
|
|||
print(is_monotonic([1, 2, 2, 3])) # Output: True
|
||||
print(is_monotonic([6, 5, 4, 4])) # Output: True
|
||||
print(is_monotonic([1, 3, 2])) # Output: False
|
||||
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -23,7 +23,7 @@ unitlist = (
|
|||
+ [cross(rs, cs) for rs in ("ABC", "DEF", "GHI") for cs in ("123", "456", "789")]
|
||||
)
|
||||
units = {s: [u for u in unitlist if s in u] for s in squares}
|
||||
peers = {s: set(sum(units[s], [])) - {s} for s in squares} # noqa: RUF017
|
||||
peers = {s: {x for u in units[s] for x in u} - {s} for s in squares}
|
||||
|
||||
|
||||
def test():
|
||||
|
@ -156,7 +156,7 @@ def solve_all(grids, name="", showif=0.0):
|
|||
times, results = zip(*[time_solve(grid) for grid in grids])
|
||||
if (n := len(grids)) > 1:
|
||||
print(
|
||||
"Solved %d of %d %s puzzles (avg %.2f secs (%d Hz), max %.2f secs)."
|
||||
"Solved %d of %d %s puzzles (avg %.2f secs (%d Hz), max %.2f secs)." # noqa: UP031
|
||||
% (sum(results), n, name, sum(times) / n, n / sum(times), max(times))
|
||||
)
|
||||
|
||||
|
@ -172,7 +172,8 @@ 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
|
||||
with open(filename) as file:
|
||||
return file.read().strip().split(sep)
|
||||
|
||||
|
||||
def random_puzzle(assignments=17):
|
||||
|
|
|
@ -30,7 +30,7 @@ def make_tree() -> Node | None:
|
|||
return tree
|
||||
|
||||
|
||||
def preorder(root: Node | None) -> Generator[int, None, None]:
|
||||
def preorder(root: Node | None) -> Generator[int]:
|
||||
"""
|
||||
Pre-order traversal visits root node, left subtree, right subtree.
|
||||
>>> list(preorder(make_tree()))
|
||||
|
@ -43,7 +43,7 @@ def preorder(root: Node | None) -> Generator[int, None, None]:
|
|||
yield from preorder(root.right)
|
||||
|
||||
|
||||
def postorder(root: Node | None) -> Generator[int, None, None]:
|
||||
def postorder(root: Node | None) -> Generator[int]:
|
||||
"""
|
||||
Post-order traversal visits left subtree, right subtree, root node.
|
||||
>>> list(postorder(make_tree()))
|
||||
|
@ -56,7 +56,7 @@ def postorder(root: Node | None) -> Generator[int, None, None]:
|
|||
yield root.data
|
||||
|
||||
|
||||
def inorder(root: Node | None) -> Generator[int, None, None]:
|
||||
def inorder(root: Node | None) -> Generator[int]:
|
||||
"""
|
||||
In-order traversal visits left subtree, root node, right subtree.
|
||||
>>> list(inorder(make_tree()))
|
||||
|
@ -69,7 +69,7 @@ def inorder(root: Node | None) -> Generator[int, None, None]:
|
|||
yield from inorder(root.right)
|
||||
|
||||
|
||||
def reverse_inorder(root: Node | None) -> Generator[int, None, None]:
|
||||
def reverse_inorder(root: Node | None) -> Generator[int]:
|
||||
"""
|
||||
Reverse in-order traversal visits right subtree, root node, left subtree.
|
||||
>>> list(reverse_inorder(make_tree()))
|
||||
|
@ -93,7 +93,7 @@ def height(root: Node | None) -> int:
|
|||
return (max(height(root.left), height(root.right)) + 1) if root else 0
|
||||
|
||||
|
||||
def level_order(root: Node | None) -> Generator[int, None, None]:
|
||||
def level_order(root: Node | None) -> Generator[int]:
|
||||
"""
|
||||
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.
|
||||
|
@ -116,9 +116,7 @@ def level_order(root: Node | None) -> Generator[int, None, None]:
|
|||
process_queue.append(node.right)
|
||||
|
||||
|
||||
def get_nodes_from_left_to_right(
|
||||
root: Node | None, level: int
|
||||
) -> Generator[int, None, None]:
|
||||
def get_nodes_from_left_to_right(root: Node | None, level: int) -> Generator[int]:
|
||||
"""
|
||||
Returns a list of nodes value from a particular level:
|
||||
Left to right direction of the binary tree.
|
||||
|
@ -128,7 +126,7 @@ def get_nodes_from_left_to_right(
|
|||
[2, 3]
|
||||
"""
|
||||
|
||||
def populate_output(root: Node | None, level: int) -> Generator[int, None, None]:
|
||||
def populate_output(root: Node | None, level: int) -> Generator[int]:
|
||||
if not root:
|
||||
return
|
||||
if level == 1:
|
||||
|
@ -140,9 +138,7 @@ def get_nodes_from_left_to_right(
|
|||
yield from populate_output(root, level)
|
||||
|
||||
|
||||
def get_nodes_from_right_to_left(
|
||||
root: Node | None, level: int
|
||||
) -> Generator[int, None, None]:
|
||||
def get_nodes_from_right_to_left(root: Node | None, level: int) -> Generator[int]:
|
||||
"""
|
||||
Returns a list of nodes value from a particular level:
|
||||
Right to left direction of the binary tree.
|
||||
|
@ -152,7 +148,7 @@ def get_nodes_from_right_to_left(
|
|||
[3, 2]
|
||||
"""
|
||||
|
||||
def populate_output(root: Node | None, level: int) -> Generator[int, None, None]:
|
||||
def populate_output(root: Node | None, level: int) -> Generator[int]:
|
||||
if not root:
|
||||
return
|
||||
if level == 1:
|
||||
|
@ -164,7 +160,7 @@ def get_nodes_from_right_to_left(
|
|||
yield from populate_output(root, level)
|
||||
|
||||
|
||||
def zigzag(root: Node | None) -> Generator[int, None, None]:
|
||||
def zigzag(root: Node | None) -> Generator[int]:
|
||||
"""
|
||||
ZigZag traverse:
|
||||
Returns a list of nodes value from left to right and right to left, alternatively.
|
||||
|
|
|
@ -56,6 +56,8 @@ class Node:
|
|||
def make_tree_seven() -> Node:
|
||||
r"""
|
||||
Return a binary tree with 7 nodes that looks like this:
|
||||
::
|
||||
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
|
@ -81,13 +83,15 @@ def make_tree_seven() -> Node:
|
|||
def make_tree_nine() -> Node:
|
||||
r"""
|
||||
Return a binary tree with 9 nodes that looks like this:
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ \
|
||||
4 5 6
|
||||
/ \ \
|
||||
7 8 9
|
||||
::
|
||||
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ \
|
||||
4 5 6
|
||||
/ \ \
|
||||
7 8 9
|
||||
|
||||
>>> tree_nine = make_tree_nine()
|
||||
>>> len(tree_nine)
|
||||
|
@ -117,23 +121,25 @@ def main() -> None:
|
|||
>>> tuple(tree.mirror())
|
||||
(6, 3, 1, 9, 5, 2, 8, 4, 7)
|
||||
|
||||
nine_tree:
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ \
|
||||
4 5 6
|
||||
/ \ \
|
||||
7 8 9
|
||||
nine_tree::
|
||||
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ \
|
||||
4 5 6
|
||||
/ \ \
|
||||
7 8 9
|
||||
|
||||
The mirrored tree looks like this::
|
||||
|
||||
The mirrored tree looks like this:
|
||||
1
|
||||
/ \
|
||||
3 2
|
||||
/ / \
|
||||
6 5 4
|
||||
/ / \
|
||||
9 8 7
|
||||
/ \
|
||||
3 2
|
||||
/ / \
|
||||
6 5 4
|
||||
/ / \
|
||||
9 8 7
|
||||
"""
|
||||
trees = {"zero": Node(0), "seven": make_tree_seven(), "nine": make_tree_nine()}
|
||||
for name, tree in trees.items():
|
||||
|
|
|
@ -32,9 +32,9 @@ def is_prime(number: int) -> bool:
|
|||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(number, int) and (
|
||||
number >= 0
|
||||
), "'number' must been an int and positive"
|
||||
assert isinstance(number, int) and (number >= 0), (
|
||||
"'number' must been an int and positive"
|
||||
)
|
||||
|
||||
if 1 < number < 4:
|
||||
# 2 and 3 are primes
|
||||
|
|
|
@ -124,9 +124,9 @@ class MinHeap:
|
|||
return len(self.heap) == 0
|
||||
|
||||
def decrease_key(self, node, new_value):
|
||||
assert (
|
||||
self.heap[self.idx_of_element[node]].val > new_value
|
||||
), "newValue must be less that current value"
|
||||
assert self.heap[self.idx_of_element[node]].val > new_value, (
|
||||
"newValue must be less that current value"
|
||||
)
|
||||
node.val = new_value
|
||||
self.heap_dict[node.name] = new_value
|
||||
self.sift_up(self.idx_of_element[node])
|
||||
|
|
|
@ -48,14 +48,14 @@ def test_build_kdtree(num_points, cube_size, num_dimensions, depth, expected_res
|
|||
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)}"
|
||||
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)}"
|
||||
assert isinstance(kdtree, KDNode), (
|
||||
f"Expected KDNode instance, got {type(kdtree)}"
|
||||
)
|
||||
|
||||
|
||||
def test_nearest_neighbour_search():
|
||||
|
|
|
@ -12,7 +12,7 @@ class _DoublyLinkedBase:
|
|||
"""A Private class (to be inherited)"""
|
||||
|
||||
class _Node:
|
||||
__slots__ = "_prev", "_data", "_next"
|
||||
__slots__ = "_data", "_next", "_prev"
|
||||
|
||||
def __init__(self, link_p, element, link_n):
|
||||
self._prev = link_p
|
||||
|
|
|
@ -33,7 +33,7 @@ class Deque:
|
|||
the number of nodes
|
||||
"""
|
||||
|
||||
__slots__ = ("_front", "_back", "_len")
|
||||
__slots__ = ("_back", "_front", "_len")
|
||||
|
||||
@dataclass
|
||||
class _Node:
|
||||
|
|
|
@ -22,18 +22,18 @@ class TestSuffixTree(unittest.TestCase):
|
|||
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."
|
||||
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."
|
||||
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."""
|
||||
|
@ -41,18 +41,18 @@ class TestSuffixTree(unittest.TestCase):
|
|||
|
||||
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."
|
||||
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."
|
||||
assert self.suffix_tree.search(substring), (
|
||||
f"Substring '{substring}' should be found."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
0
docs/source/__init__.py
Normal file
0
docs/source/__init__.py
Normal file
|
@ -8,9 +8,10 @@ from __future__ import annotations
|
|||
|
||||
def all_construct(target: str, word_bank: list[str] | None = None) -> list[list[str]]:
|
||||
"""
|
||||
returns the list containing all the possible
|
||||
combinations a string(target) can be constructed from
|
||||
the given list of substrings(word_bank)
|
||||
returns the list containing all the possible
|
||||
combinations a string(`target`) can be constructed from
|
||||
the given list of substrings(`word_bank`)
|
||||
|
||||
>>> all_construct("hello", ["he", "l", "o"])
|
||||
[['he', 'l', 'l', 'o']]
|
||||
>>> all_construct("purple",["purp","p","ur","le","purpl"])
|
||||
|
|
|
@ -25,9 +25,9 @@ def climb_stairs(number_of_steps: int) -> int:
|
|||
...
|
||||
AssertionError: number_of_steps needs to be positive integer, your input -7
|
||||
"""
|
||||
assert (
|
||||
isinstance(number_of_steps, int) and number_of_steps > 0
|
||||
), f"number_of_steps needs to be positive integer, your input {number_of_steps}"
|
||||
assert isinstance(number_of_steps, int) and number_of_steps > 0, (
|
||||
f"number_of_steps needs to be positive integer, your input {number_of_steps}"
|
||||
)
|
||||
if number_of_steps == 1:
|
||||
return 1
|
||||
previous, current = 1, 1
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
"""
|
||||
Question:
|
||||
You are given an array of distinct integers and you have to tell how many
|
||||
different ways of selecting the elements from the array are there such that
|
||||
the sum of chosen elements is equal to the target number tar.
|
||||
You are given an array of distinct integers and you have to tell how many
|
||||
different ways of selecting the elements from the array are there such that
|
||||
the sum of chosen elements is equal to the target number tar.
|
||||
|
||||
Example
|
||||
|
||||
Input:
|
||||
N = 3
|
||||
target = 5
|
||||
array = [1, 2, 5]
|
||||
* N = 3
|
||||
* target = 5
|
||||
* array = [1, 2, 5]
|
||||
|
||||
Output:
|
||||
9
|
||||
9
|
||||
|
||||
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.
|
||||
The basic idea is to go over recursively to find the way such that the sum
|
||||
of chosen elements is `target`. 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.
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
|
||||
def fizz_buzz(number: int, iterations: int) -> str:
|
||||
"""
|
||||
Plays FizzBuzz.
|
||||
Prints Fizz if number is a multiple of 3.
|
||||
Prints Buzz if its a multiple of 5.
|
||||
Prints FizzBuzz if its a multiple of both 3 and 5 or 15.
|
||||
Else Prints The Number Itself.
|
||||
| Plays FizzBuzz.
|
||||
| Prints Fizz if number is a multiple of ``3``.
|
||||
| Prints Buzz if its a multiple of ``5``.
|
||||
| Prints FizzBuzz if its a multiple of both ``3`` and ``5`` or ``15``.
|
||||
| Else Prints The Number Itself.
|
||||
|
||||
>>> fizz_buzz(1,7)
|
||||
'1 2 Fizz 4 Buzz Fizz 7 '
|
||||
>>> fizz_buzz(1,0)
|
||||
|
|
|
@ -37,9 +37,9 @@ def list_of_submasks(mask: int) -> list[int]:
|
|||
|
||||
"""
|
||||
|
||||
assert (
|
||||
isinstance(mask, int) and mask > 0
|
||||
), f"mask needs to be positive integer, your input {mask}"
|
||||
assert isinstance(mask, int) and mask > 0, (
|
||||
f"mask needs to be positive integer, your input {mask}"
|
||||
)
|
||||
|
||||
"""
|
||||
first submask iterated will be mask itself then operation will be performed
|
||||
|
|
|
@ -11,7 +11,7 @@ def mf_knapsack(i, wt, val, j):
|
|||
"""
|
||||
This code involves the concept of memory functions. Here we solve the subproblems
|
||||
which are needed unlike the below example
|
||||
F is a 2D array with -1s filled up
|
||||
F is a 2D array with ``-1`` s filled up
|
||||
"""
|
||||
global f # a global dp table for knapsack
|
||||
if f[i][j] < 0:
|
||||
|
@ -45,22 +45,24 @@ def knapsack_with_example_solution(w: int, wt: list, val: list):
|
|||
the several possible optimal subsets.
|
||||
|
||||
Parameters
|
||||
---------
|
||||
----------
|
||||
|
||||
W: int, the total maximum weight for the given knapsack problem.
|
||||
wt: list, the vector of weights for all items where wt[i] is the weight
|
||||
of the i-th item.
|
||||
val: list, the vector of values for all items where val[i] is the value
|
||||
of the i-th item
|
||||
* `w`: int, the total maximum weight for the given knapsack problem.
|
||||
* `wt`: list, the vector of weights for all items where ``wt[i]`` is the weight
|
||||
of the ``i``-th item.
|
||||
* `val`: list, the vector of values for all items where ``val[i]`` is the value
|
||||
of the ``i``-th item
|
||||
|
||||
Returns
|
||||
-------
|
||||
optimal_val: float, the optimal value for the given knapsack problem
|
||||
example_optional_set: set, the indices of one of the optimal subsets
|
||||
which gave rise to the optimal value.
|
||||
|
||||
* `optimal_val`: float, the optimal value for the given knapsack problem
|
||||
* `example_optional_set`: set, the indices of one of the optimal subsets
|
||||
which gave rise to the optimal value.
|
||||
|
||||
Examples
|
||||
-------
|
||||
--------
|
||||
|
||||
>>> knapsack_with_example_solution(10, [1, 3, 5, 2], [10, 20, 100, 22])
|
||||
(142, {2, 3, 4})
|
||||
>>> knapsack_with_example_solution(6, [4, 3, 2, 3], [3, 2, 4, 4])
|
||||
|
@ -104,19 +106,19 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set):
|
|||
a filled DP table and the vector of weights
|
||||
|
||||
Parameters
|
||||
---------
|
||||
----------
|
||||
|
||||
dp: list of list, the table of a solved integer weight dynamic programming problem
|
||||
|
||||
wt: list or tuple, the vector of weights of the items
|
||||
i: int, the index of the item under consideration
|
||||
j: int, the current possible maximum weight
|
||||
optimal_set: set, the optimal subset so far. This gets modified by the function.
|
||||
* `dp`: list of list, the table of a solved integer weight dynamic programming
|
||||
problem
|
||||
* `wt`: list or tuple, the vector of weights of the items
|
||||
* `i`: int, the index of the item under consideration
|
||||
* `j`: int, the current possible maximum weight
|
||||
* `optimal_set`: set, the optimal subset so far. This gets modified by the function.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
|
||||
``None``
|
||||
"""
|
||||
# for the current item i at a maximum weight j to be part of an optimal subset,
|
||||
# the optimal value at (i, j) must be greater than the optimal value at (i-1, j).
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
"""
|
||||
Longest Common Substring Problem Statement: Given two sequences, find the
|
||||
longest common substring present in both of them. A substring is
|
||||
necessarily continuous.
|
||||
Example: "abcdef" and "xabded" have two longest common substrings, "ab" or "de".
|
||||
Therefore, algorithm should return any one of them.
|
||||
Longest Common Substring Problem Statement:
|
||||
Given two sequences, find the
|
||||
longest common substring present in both of them. A substring is
|
||||
necessarily continuous.
|
||||
|
||||
Example:
|
||||
``abcdef`` and ``xabded`` have two longest common substrings, ``ab`` or ``de``.
|
||||
Therefore, algorithm should return any one of them.
|
||||
"""
|
||||
|
||||
|
||||
def longest_common_substring(text1: str, text2: str) -> str:
|
||||
"""
|
||||
Finds the longest common substring between two strings.
|
||||
|
||||
>>> longest_common_substring("", "")
|
||||
''
|
||||
>>> longest_common_substring("a","")
|
||||
|
|
|
@ -4,11 +4,13 @@ Author : Mehdi ALAOUI
|
|||
This is a pure Python implementation of Dynamic Programming solution to the longest
|
||||
increasing subsequence of a given sequence.
|
||||
|
||||
The problem is :
|
||||
Given an array, to find the longest and increasing sub-array in that given array and
|
||||
return it.
|
||||
Example: [10, 22, 9, 33, 21, 50, 41, 60, 80] as input will return
|
||||
[10, 22, 33, 41, 60, 80] as output
|
||||
The problem is:
|
||||
Given an array, to find the longest and increasing sub-array in that given array and
|
||||
return it.
|
||||
|
||||
Example:
|
||||
``[10, 22, 9, 33, 21, 50, 41, 60, 80]`` as input will return
|
||||
``[10, 22, 33, 41, 60, 80]`` as output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
@ -17,12 +19,15 @@ from __future__ import annotations
|
|||
def longest_subsequence(array: list[int]) -> list[int]: # This function is recursive
|
||||
"""
|
||||
Some examples
|
||||
|
||||
>>> longest_subsequence([10, 22, 9, 33, 21, 50, 41, 60, 80])
|
||||
[10, 22, 33, 41, 60, 80]
|
||||
>>> longest_subsequence([4, 8, 7, 5, 1, 12, 2, 3, 9])
|
||||
[1, 2, 3, 9]
|
||||
>>> longest_subsequence([28, 26, 12, 23, 35, 39])
|
||||
[12, 23, 35, 39]
|
||||
>>> longest_subsequence([9, 8, 7, 6, 5, 7])
|
||||
[8]
|
||||
[5, 7]
|
||||
>>> longest_subsequence([1, 1, 1])
|
||||
[1, 1, 1]
|
||||
>>> longest_subsequence([])
|
||||
|
@ -41,7 +46,7 @@ def longest_subsequence(array: list[int]) -> list[int]: # This function is recu
|
|||
while not is_found and i < array_length:
|
||||
if array[i] < pivot:
|
||||
is_found = True
|
||||
temp_array = [element for element in array[i:] if element >= array[i]]
|
||||
temp_array = array[i:]
|
||||
temp_array = longest_subsequence(temp_array)
|
||||
if len(temp_array) > len(longest_subseq):
|
||||
longest_subseq = temp_array
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
"""
|
||||
Author : Sanjay Muthu <https://github.com/XenoBytesX>
|
||||
|
||||
This is a pure Python implementation of Dynamic Programming solution to the longest
|
||||
increasing subsequence of a given sequence.
|
||||
|
||||
The problem is:
|
||||
Given an array, to find the longest and increasing sub-array in that given array and
|
||||
return it.
|
||||
|
||||
Example:
|
||||
``[10, 22, 9, 33, 21, 50, 41, 60, 80]`` as input will return
|
||||
``[10, 22, 33, 50, 60, 80]`` as output
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
|
||||
|
||||
def longest_subsequence(array: list[int]) -> list[int]:
|
||||
"""
|
||||
Some examples
|
||||
|
||||
>>> longest_subsequence([10, 22, 9, 33, 21, 50, 41, 60, 80])
|
||||
[10, 22, 33, 50, 60, 80]
|
||||
>>> longest_subsequence([4, 8, 7, 5, 1, 12, 2, 3, 9])
|
||||
[1, 2, 3, 9]
|
||||
>>> longest_subsequence([9, 8, 7, 6, 5, 7])
|
||||
[7, 7]
|
||||
>>> longest_subsequence([28, 26, 12, 23, 35, 39])
|
||||
[12, 23, 35, 39]
|
||||
>>> longest_subsequence([1, 1, 1])
|
||||
[1, 1, 1]
|
||||
>>> longest_subsequence([])
|
||||
[]
|
||||
"""
|
||||
n = len(array)
|
||||
# The longest increasing subsequence ending at array[i]
|
||||
longest_increasing_subsequence = []
|
||||
for i in range(n):
|
||||
longest_increasing_subsequence.append([array[i]])
|
||||
|
||||
for i in range(1, n):
|
||||
for prev in range(i):
|
||||
# If array[prev] is less than or equal to array[i], then
|
||||
# longest_increasing_subsequence[prev] + array[i]
|
||||
# is a valid increasing subsequence
|
||||
|
||||
# longest_increasing_subsequence[i] is only set to
|
||||
# longest_increasing_subsequence[prev] + array[i] if the length is longer.
|
||||
|
||||
if array[prev] <= array[i] and len(
|
||||
longest_increasing_subsequence[prev]
|
||||
) + 1 > len(longest_increasing_subsequence[i]):
|
||||
longest_increasing_subsequence[i] = copy.copy(
|
||||
longest_increasing_subsequence[prev]
|
||||
)
|
||||
longest_increasing_subsequence[i].append(array[i])
|
||||
|
||||
result: list[int] = []
|
||||
for i in range(n):
|
||||
if len(longest_increasing_subsequence[i]) > len(result):
|
||||
result = longest_increasing_subsequence[i]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -1,42 +1,48 @@
|
|||
"""
|
||||
Find the minimum number of multiplications needed to multiply chain of matrices.
|
||||
Reference: https://www.geeksforgeeks.org/matrix-chain-multiplication-dp-8/
|
||||
| Find the minimum number of multiplications needed to multiply chain of matrices.
|
||||
| Reference: https://www.geeksforgeeks.org/matrix-chain-multiplication-dp-8/
|
||||
|
||||
The algorithm has interesting real-world applications. Example:
|
||||
1. Image transformations in Computer Graphics as images are composed of matrix.
|
||||
2. Solve complex polynomial equations in the field of algebra using least processing
|
||||
power.
|
||||
3. Calculate overall impact of macroeconomic decisions as economic equations involve a
|
||||
number of variables.
|
||||
4. Self-driving car navigation can be made more accurate as matrix multiplication can
|
||||
accurately determine position and orientation of obstacles in short time.
|
||||
The algorithm has interesting real-world applications.
|
||||
|
||||
Python doctests can be run with the following command:
|
||||
python -m doctest -v matrix_chain_multiply.py
|
||||
Example:
|
||||
1. Image transformations in Computer Graphics as images are composed of matrix.
|
||||
2. Solve complex polynomial equations in the field of algebra using least processing
|
||||
power.
|
||||
3. Calculate overall impact of macroeconomic decisions as economic equations involve a
|
||||
number of variables.
|
||||
4. Self-driving car navigation can be made more accurate as matrix multiplication can
|
||||
accurately determine position and orientation of obstacles in short time.
|
||||
|
||||
Given a sequence arr[] that represents chain of 2D matrices such that the dimension of
|
||||
the ith matrix is arr[i-1]*arr[i].
|
||||
So suppose arr = [40, 20, 30, 10, 30] means we have 4 matrices of dimensions
|
||||
40*20, 20*30, 30*10 and 10*30.
|
||||
Python doctests can be run with the following command::
|
||||
|
||||
matrix_chain_multiply() returns an integer denoting minimum number of multiplications to
|
||||
multiply the chain.
|
||||
python -m doctest -v matrix_chain_multiply.py
|
||||
|
||||
Given a sequence ``arr[]`` that represents chain of 2D matrices such that the dimension
|
||||
of the ``i`` th matrix is ``arr[i-1]*arr[i]``.
|
||||
So suppose ``arr = [40, 20, 30, 10, 30]`` means we have ``4`` matrices of dimensions
|
||||
``40*20``, ``20*30``, ``30*10`` and ``10*30``.
|
||||
|
||||
``matrix_chain_multiply()`` returns an integer denoting minimum number of
|
||||
multiplications to multiply the chain.
|
||||
|
||||
We do not need to perform actual multiplication here.
|
||||
We only need to decide the order in which to perform the multiplication.
|
||||
|
||||
Hints:
|
||||
1. Number of multiplications (ie cost) to multiply 2 matrices
|
||||
of size m*p and p*n is m*p*n.
|
||||
2. Cost of matrix multiplication is associative ie (M1*M2)*M3 != M1*(M2*M3)
|
||||
3. Matrix multiplication is not commutative. So, M1*M2 does not mean M2*M1 can be done.
|
||||
4. To determine the required order, we can try different combinations.
|
||||
1. Number of multiplications (ie cost) to multiply ``2`` matrices
|
||||
of size ``m*p`` and ``p*n`` is ``m*p*n``.
|
||||
2. Cost of matrix multiplication is not associative ie ``(M1*M2)*M3 != M1*(M2*M3)``
|
||||
3. Matrix multiplication is not commutative. So, ``M1*M2`` does not mean ``M2*M1``
|
||||
can be done.
|
||||
4. To determine the required order, we can try different combinations.
|
||||
|
||||
So, this problem has overlapping sub-problems and can be solved using recursion.
|
||||
We use Dynamic Programming for optimal time complexity.
|
||||
|
||||
Example input:
|
||||
arr = [40, 20, 30, 10, 30]
|
||||
output: 26000
|
||||
``arr = [40, 20, 30, 10, 30]``
|
||||
output:
|
||||
``26000``
|
||||
"""
|
||||
|
||||
from collections.abc import Iterator
|
||||
|
@ -50,25 +56,25 @@ def matrix_chain_multiply(arr: list[int]) -> int:
|
|||
Find the minimum number of multiplcations required to multiply the chain of matrices
|
||||
|
||||
Args:
|
||||
arr: The input array of integers.
|
||||
`arr`: The input array of integers.
|
||||
|
||||
Returns:
|
||||
Minimum number of multiplications needed to multiply the chain
|
||||
|
||||
Examples:
|
||||
>>> matrix_chain_multiply([1, 2, 3, 4, 3])
|
||||
30
|
||||
>>> matrix_chain_multiply([10])
|
||||
0
|
||||
>>> matrix_chain_multiply([10, 20])
|
||||
0
|
||||
>>> matrix_chain_multiply([19, 2, 19])
|
||||
722
|
||||
>>> matrix_chain_multiply(list(range(1, 100)))
|
||||
323398
|
||||
|
||||
# >>> matrix_chain_multiply(list(range(1, 251)))
|
||||
# 2626798
|
||||
>>> matrix_chain_multiply([1, 2, 3, 4, 3])
|
||||
30
|
||||
>>> matrix_chain_multiply([10])
|
||||
0
|
||||
>>> matrix_chain_multiply([10, 20])
|
||||
0
|
||||
>>> matrix_chain_multiply([19, 2, 19])
|
||||
722
|
||||
>>> matrix_chain_multiply(list(range(1, 100)))
|
||||
323398
|
||||
>>> # matrix_chain_multiply(list(range(1, 251)))
|
||||
# 2626798
|
||||
"""
|
||||
if len(arr) < 2:
|
||||
return 0
|
||||
|
@ -93,8 +99,10 @@ def matrix_chain_multiply(arr: list[int]) -> int:
|
|||
def matrix_chain_order(dims: list[int]) -> int:
|
||||
"""
|
||||
Source: https://en.wikipedia.org/wiki/Matrix_chain_multiplication
|
||||
|
||||
The dynamic programming solution is faster than cached the recursive solution and
|
||||
can handle larger inputs.
|
||||
|
||||
>>> matrix_chain_order([1, 2, 3, 4, 3])
|
||||
30
|
||||
>>> matrix_chain_order([10])
|
||||
|
@ -105,8 +113,7 @@ def matrix_chain_order(dims: list[int]) -> int:
|
|||
722
|
||||
>>> matrix_chain_order(list(range(1, 100)))
|
||||
323398
|
||||
|
||||
# >>> matrix_chain_order(list(range(1, 251))) # Max before RecursionError is raised
|
||||
>>> # matrix_chain_order(list(range(1, 251))) # Max before RecursionError is raised
|
||||
# 2626798
|
||||
"""
|
||||
|
||||
|
@ -127,7 +134,7 @@ def elapsed_time(msg: str) -> Iterator:
|
|||
|
||||
start = perf_counter_ns()
|
||||
yield
|
||||
print(f"Finished: {msg} in {(perf_counter_ns() - start) / 10 ** 9} seconds.")
|
||||
print(f"Finished: {msg} in {(perf_counter_ns() - start) / 10**9} seconds.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
def max_product_subarray(numbers: list[int]) -> int:
|
||||
"""
|
||||
Returns the maximum product that can be obtained by multiplying a
|
||||
contiguous subarray of the given integer list `nums`.
|
||||
contiguous subarray of the given integer list `numbers`.
|
||||
|
||||
Example:
|
||||
|
||||
>>> max_product_subarray([2, 3, -2, 4])
|
||||
6
|
||||
>>> max_product_subarray((-2, 0, -1))
|
||||
|
|
|
@ -5,6 +5,7 @@ import sys
|
|||
def minimum_squares_to_represent_a_number(number: int) -> int:
|
||||
"""
|
||||
Count the number of minimum squares to represent a number
|
||||
|
||||
>>> minimum_squares_to_represent_a_number(25)
|
||||
1
|
||||
>>> minimum_squares_to_represent_a_number(37)
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
"""
|
||||
Regex matching check if a text matches pattern or not.
|
||||
Pattern:
|
||||
'.' Matches any single character.
|
||||
'*' Matches zero or more of the preceding element.
|
||||
|
||||
1. ``.`` Matches any single character.
|
||||
2. ``*`` Matches zero or more of the preceding element.
|
||||
|
||||
More info:
|
||||
https://medium.com/trick-the-interviwer/regular-expression-matching-9972eb74c03
|
||||
"""
|
||||
|
||||
|
||||
def recursive_match(text: str, pattern: str) -> bool:
|
||||
"""
|
||||
r"""
|
||||
Recursive matching algorithm.
|
||||
|
||||
Time complexity: O(2 ^ (|text| + |pattern|))
|
||||
Space complexity: Recursion depth is O(|text| + |pattern|).
|
||||
| Time complexity: O(2^(\|text\| + \|pattern\|))
|
||||
| Space complexity: Recursion depth is O(\|text\| + \|pattern\|).
|
||||
|
||||
:param text: Text to match.
|
||||
:param pattern: Pattern to match.
|
||||
:return: True if text matches pattern, False otherwise.
|
||||
:return: ``True`` if `text` matches `pattern`, ``False`` otherwise.
|
||||
|
||||
>>> recursive_match('abc', 'a.c')
|
||||
True
|
||||
|
@ -48,15 +50,15 @@ def recursive_match(text: str, pattern: str) -> bool:
|
|||
|
||||
|
||||
def dp_match(text: str, pattern: str) -> bool:
|
||||
"""
|
||||
r"""
|
||||
Dynamic programming matching algorithm.
|
||||
|
||||
Time complexity: O(|text| * |pattern|)
|
||||
Space complexity: O(|text| * |pattern|)
|
||||
| Time complexity: O(\|text\| * \|pattern\|)
|
||||
| Space complexity: O(\|text\| * \|pattern\|)
|
||||
|
||||
:param text: Text to match.
|
||||
:param pattern: Pattern to match.
|
||||
:return: True if text matches pattern, False otherwise.
|
||||
:return: ``True`` if `text` matches `pattern`, ``False`` otherwise.
|
||||
|
||||
>>> dp_match('abc', 'a.c')
|
||||
True
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
This module provides two implementations for the rod-cutting problem:
|
||||
1. A naive recursive implementation which has an exponential runtime
|
||||
2. Two dynamic programming implementations which have quadratic runtime
|
||||
1. A naive recursive implementation which has an exponential runtime
|
||||
2. Two dynamic programming implementations which have quadratic runtime
|
||||
|
||||
The rod-cutting problem is the problem of finding the maximum possible revenue
|
||||
obtainable from a rod of length ``n`` given a list of prices for each integral piece
|
||||
|
@ -20,18 +20,21 @@ def naive_cut_rod_recursive(n: int, prices: list):
|
|||
Runtime: O(2^n)
|
||||
|
||||
Arguments
|
||||
-------
|
||||
n: int, the length of the rod
|
||||
prices: list, the prices for each piece of rod. ``p[i-i]`` is the
|
||||
price for a rod of length ``i``
|
||||
---------
|
||||
|
||||
* `n`: int, the length of the rod
|
||||
* `prices`: list, the prices for each piece of rod. ``p[i-i]`` is the
|
||||
price for a rod of length ``i``
|
||||
|
||||
Returns
|
||||
-------
|
||||
The maximum revenue obtainable for a rod of length n given the list of prices
|
||||
|
||||
The maximum revenue obtainable for a rod of length `n` given the list of prices
|
||||
for each piece.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
>>> naive_cut_rod_recursive(4, [1, 5, 8, 9])
|
||||
10
|
||||
>>> naive_cut_rod_recursive(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30])
|
||||
|
@ -54,28 +57,30 @@ def top_down_cut_rod(n: int, prices: list):
|
|||
"""
|
||||
Constructs a top-down dynamic programming solution for the rod-cutting
|
||||
problem via memoization. This function serves as a wrapper for
|
||||
_top_down_cut_rod_recursive
|
||||
``_top_down_cut_rod_recursive``
|
||||
|
||||
Runtime: O(n^2)
|
||||
|
||||
Arguments
|
||||
--------
|
||||
n: int, the length of the rod
|
||||
prices: list, the prices for each piece of rod. ``p[i-i]`` is the
|
||||
price for a rod of length ``i``
|
||||
---------
|
||||
|
||||
Note
|
||||
----
|
||||
For convenience and because Python's lists using 0-indexing, length(max_rev) =
|
||||
n + 1, to accommodate for the revenue obtainable from a rod of length 0.
|
||||
* `n`: int, the length of the rod
|
||||
* `prices`: list, the prices for each piece of rod. ``p[i-i]`` is the
|
||||
price for a rod of length ``i``
|
||||
|
||||
.. note::
|
||||
For convenience and because Python's lists using ``0``-indexing, ``length(max_rev)
|
||||
= n + 1``, to accommodate for the revenue obtainable from a rod of length ``0``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
The maximum revenue obtainable for a rod of length n given the list of prices
|
||||
|
||||
The maximum revenue obtainable for a rod of length `n` given the list of prices
|
||||
for each piece.
|
||||
|
||||
Examples
|
||||
-------
|
||||
--------
|
||||
|
||||
>>> top_down_cut_rod(4, [1, 5, 8, 9])
|
||||
10
|
||||
>>> top_down_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30])
|
||||
|
@ -94,16 +99,18 @@ def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list):
|
|||
Runtime: O(n^2)
|
||||
|
||||
Arguments
|
||||
--------
|
||||
n: int, the length of the rod
|
||||
prices: list, the prices for each piece of rod. ``p[i-i]`` is the
|
||||
price for a rod of length ``i``
|
||||
max_rev: list, the computed maximum revenue for a piece of rod.
|
||||
``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i``
|
||||
---------
|
||||
|
||||
* `n`: int, the length of the rod
|
||||
* `prices`: list, the prices for each piece of rod. ``p[i-i]`` is the
|
||||
price for a rod of length ``i``
|
||||
* `max_rev`: list, the computed maximum revenue for a piece of rod.
|
||||
``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i``
|
||||
|
||||
Returns
|
||||
-------
|
||||
The maximum revenue obtainable for a rod of length n given the list of prices
|
||||
|
||||
The maximum revenue obtainable for a rod of length `n` given the list of prices
|
||||
for each piece.
|
||||
"""
|
||||
if max_rev[n] >= 0:
|
||||
|
@ -130,18 +137,21 @@ def bottom_up_cut_rod(n: int, prices: list):
|
|||
Runtime: O(n^2)
|
||||
|
||||
Arguments
|
||||
----------
|
||||
n: int, the maximum length of the rod.
|
||||
prices: list, the prices for each piece of rod. ``p[i-i]`` is the
|
||||
price for a rod of length ``i``
|
||||
---------
|
||||
|
||||
* `n`: int, the maximum length of the rod.
|
||||
* `prices`: list, the prices for each piece of rod. ``p[i-i]`` is the
|
||||
price for a rod of length ``i``
|
||||
|
||||
Returns
|
||||
-------
|
||||
The maximum revenue obtainable from cutting a rod of length n given
|
||||
|
||||
The maximum revenue obtainable from cutting a rod of length `n` given
|
||||
the prices for each piece of rod p.
|
||||
|
||||
Examples
|
||||
-------
|
||||
--------
|
||||
|
||||
>>> bottom_up_cut_rod(4, [1, 5, 8, 9])
|
||||
10
|
||||
>>> bottom_up_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30])
|
||||
|
@ -168,13 +178,12 @@ def _enforce_args(n: int, prices: list):
|
|||
"""
|
||||
Basic checks on the arguments to the rod-cutting algorithms
|
||||
|
||||
n: int, the length of the rod
|
||||
prices: list, the price list for each piece of rod.
|
||||
* `n`: int, the length of the rod
|
||||
* `prices`: list, the price list for each piece of rod.
|
||||
|
||||
Throws ValueError:
|
||||
|
||||
if n is negative or there are fewer items in the price list than the length of
|
||||
the rod
|
||||
Throws ``ValueError``:
|
||||
if `n` is negative or there are fewer items in the price list than the length of
|
||||
the rod
|
||||
"""
|
||||
if n < 0:
|
||||
msg = f"n must be greater than or equal to 0. Got n = {n}"
|
||||
|
|
|
@ -1,38 +1,41 @@
|
|||
def subset_combinations(elements: list[int], n: int) -> list:
|
||||
"""
|
||||
Compute n-element combinations from a given list using dynamic programming.
|
||||
|
||||
Args:
|
||||
elements: The list of elements from which combinations will be generated.
|
||||
n: The number of elements in each combination.
|
||||
* `elements`: The list of elements from which combinations will be generated.
|
||||
* `n`: The number of elements in each combination.
|
||||
|
||||
Returns:
|
||||
A list of tuples, each representing a combination of n elements.
|
||||
>>> subset_combinations(elements=[10, 20, 30, 40], n=2)
|
||||
[(10, 20), (10, 30), (10, 40), (20, 30), (20, 40), (30, 40)]
|
||||
>>> subset_combinations(elements=[1, 2, 3], n=1)
|
||||
[(1,), (2,), (3,)]
|
||||
>>> subset_combinations(elements=[1, 2, 3], n=3)
|
||||
[(1, 2, 3)]
|
||||
>>> subset_combinations(elements=[42], n=1)
|
||||
[(42,)]
|
||||
>>> subset_combinations(elements=[6, 7, 8, 9], n=4)
|
||||
[(6, 7, 8, 9)]
|
||||
>>> subset_combinations(elements=[10, 20, 30, 40, 50], n=0)
|
||||
[()]
|
||||
>>> subset_combinations(elements=[1, 2, 3, 4], n=2)
|
||||
[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
|
||||
>>> subset_combinations(elements=[1, 'apple', 3.14], n=2)
|
||||
[(1, 'apple'), (1, 3.14), ('apple', 3.14)]
|
||||
>>> subset_combinations(elements=['single'], n=0)
|
||||
[()]
|
||||
>>> subset_combinations(elements=[], n=9)
|
||||
[]
|
||||
>>> from itertools import combinations
|
||||
>>> all(subset_combinations(items, n) == list(combinations(items, n))
|
||||
... for items, n in (
|
||||
... ([10, 20, 30, 40], 2), ([1, 2, 3], 1), ([1, 2, 3], 3), ([42], 1),
|
||||
... ([6, 7, 8, 9], 4), ([10, 20, 30, 40, 50], 1), ([1, 2, 3, 4], 2),
|
||||
... ([1, 'apple', 3.14], 2), (['single'], 0), ([], 9)))
|
||||
True
|
||||
A list of tuples, each representing a combination of `n` elements.
|
||||
|
||||
>>> subset_combinations(elements=[10, 20, 30, 40], n=2)
|
||||
[(10, 20), (10, 30), (10, 40), (20, 30), (20, 40), (30, 40)]
|
||||
>>> subset_combinations(elements=[1, 2, 3], n=1)
|
||||
[(1,), (2,), (3,)]
|
||||
>>> subset_combinations(elements=[1, 2, 3], n=3)
|
||||
[(1, 2, 3)]
|
||||
>>> subset_combinations(elements=[42], n=1)
|
||||
[(42,)]
|
||||
>>> subset_combinations(elements=[6, 7, 8, 9], n=4)
|
||||
[(6, 7, 8, 9)]
|
||||
>>> subset_combinations(elements=[10, 20, 30, 40, 50], n=0)
|
||||
[()]
|
||||
>>> subset_combinations(elements=[1, 2, 3, 4], n=2)
|
||||
[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
|
||||
>>> subset_combinations(elements=[1, 'apple', 3.14], n=2)
|
||||
[(1, 'apple'), (1, 3.14), ('apple', 3.14)]
|
||||
>>> subset_combinations(elements=['single'], n=0)
|
||||
[()]
|
||||
>>> subset_combinations(elements=[], n=9)
|
||||
[]
|
||||
>>> from itertools import combinations
|
||||
>>> all(subset_combinations(items, n) == list(combinations(items, n))
|
||||
... for items, n in (
|
||||
... ([10, 20, 30, 40], 2), ([1, 2, 3], 1), ([1, 2, 3], 3), ([42], 1),
|
||||
... ([6, 7, 8, 9], 4), ([10, 20, 30, 40, 50], 1), ([1, 2, 3, 4], 2),
|
||||
... ([1, 'apple', 3.14], 2), (['single'], 0), ([], 9)))
|
||||
True
|
||||
"""
|
||||
r = len(elements)
|
||||
if n > r:
|
||||
|
|
|
@ -9,119 +9,102 @@ def viterbi(
|
|||
emission_probabilities: dict,
|
||||
) -> list:
|
||||
"""
|
||||
Viterbi Algorithm, to find the most likely path of
|
||||
states from the start and the expected output.
|
||||
https://en.wikipedia.org/wiki/Viterbi_algorithm
|
||||
sdafads
|
||||
Wikipedia example
|
||||
>>> observations = ["normal", "cold", "dizzy"]
|
||||
>>> states = ["Healthy", "Fever"]
|
||||
>>> start_p = {"Healthy": 0.6, "Fever": 0.4}
|
||||
>>> trans_p = {
|
||||
... "Healthy": {"Healthy": 0.7, "Fever": 0.3},
|
||||
... "Fever": {"Healthy": 0.4, "Fever": 0.6},
|
||||
... }
|
||||
>>> emit_p = {
|
||||
... "Healthy": {"normal": 0.5, "cold": 0.4, "dizzy": 0.1},
|
||||
... "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6},
|
||||
... }
|
||||
>>> viterbi(observations, states, start_p, trans_p, emit_p)
|
||||
['Healthy', 'Healthy', 'Fever']
|
||||
Viterbi Algorithm, to find the most likely path of
|
||||
states from the start and the expected output.
|
||||
|
||||
>>> viterbi((), states, start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
https://en.wikipedia.org/wiki/Viterbi_algorithm
|
||||
|
||||
>>> viterbi(observations, (), start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
Wikipedia example
|
||||
|
||||
>>> viterbi(observations, states, {}, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
|
||||
>>> viterbi(observations, states, start_p, {}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
|
||||
>>> viterbi(observations, states, start_p, trans_p, {})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
|
||||
>>> viterbi("invalid", states, start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: observations_space must be a list
|
||||
|
||||
>>> viterbi(["valid", 123], states, start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: observations_space must be a list of strings
|
||||
|
||||
>>> viterbi(observations, "invalid", start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: states_space must be a list
|
||||
|
||||
>>> viterbi(observations, ["valid", 123], start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: states_space must be a list of strings
|
||||
|
||||
>>> viterbi(observations, states, "invalid", trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: initial_probabilities must be a dict
|
||||
|
||||
>>> viterbi(observations, states, {2:2}, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: initial_probabilities all keys must be strings
|
||||
|
||||
>>> viterbi(observations, states, {"a":2}, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: initial_probabilities all values must be float
|
||||
|
||||
>>> viterbi(observations, states, start_p, "invalid", emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities must be a dict
|
||||
|
||||
>>> viterbi(observations, states, start_p, {"a":2}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities all values must be dict
|
||||
|
||||
>>> viterbi(observations, states, start_p, {2:{2:2}}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities all keys must be strings
|
||||
|
||||
>>> viterbi(observations, states, start_p, {"a":{2:2}}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities all keys must be strings
|
||||
|
||||
>>> viterbi(observations, states, start_p, {"a":{"b":2}}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities nested dictionary all values must be float
|
||||
|
||||
>>> viterbi(observations, states, start_p, trans_p, "invalid")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: emission_probabilities must be a dict
|
||||
|
||||
>>> viterbi(observations, states, start_p, trans_p, None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
>>> observations = ["normal", "cold", "dizzy"]
|
||||
>>> states = ["Healthy", "Fever"]
|
||||
>>> start_p = {"Healthy": 0.6, "Fever": 0.4}
|
||||
>>> trans_p = {
|
||||
... "Healthy": {"Healthy": 0.7, "Fever": 0.3},
|
||||
... "Fever": {"Healthy": 0.4, "Fever": 0.6},
|
||||
... }
|
||||
>>> emit_p = {
|
||||
... "Healthy": {"normal": 0.5, "cold": 0.4, "dizzy": 0.1},
|
||||
... "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6},
|
||||
... }
|
||||
>>> viterbi(observations, states, start_p, trans_p, emit_p)
|
||||
['Healthy', 'Healthy', 'Fever']
|
||||
>>> viterbi((), states, start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
>>> viterbi(observations, (), start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
>>> viterbi(observations, states, {}, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
>>> viterbi(observations, states, start_p, {}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
>>> viterbi(observations, states, start_p, trans_p, {})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
>>> viterbi("invalid", states, start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: observations_space must be a list
|
||||
>>> viterbi(["valid", 123], states, start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: observations_space must be a list of strings
|
||||
>>> viterbi(observations, "invalid", start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: states_space must be a list
|
||||
>>> viterbi(observations, ["valid", 123], start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: states_space must be a list of strings
|
||||
>>> viterbi(observations, states, "invalid", trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: initial_probabilities must be a dict
|
||||
>>> viterbi(observations, states, {2:2}, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: initial_probabilities all keys must be strings
|
||||
>>> viterbi(observations, states, {"a":2}, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: initial_probabilities all values must be float
|
||||
>>> viterbi(observations, states, start_p, "invalid", emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities must be a dict
|
||||
>>> viterbi(observations, states, start_p, {"a":2}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities all values must be dict
|
||||
>>> viterbi(observations, states, start_p, {2:{2:2}}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities all keys must be strings
|
||||
>>> viterbi(observations, states, start_p, {"a":{2:2}}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities all keys must be strings
|
||||
>>> viterbi(observations, states, start_p, {"a":{"b":2}}, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: transition_probabilities nested dictionary all values must be float
|
||||
>>> viterbi(observations, states, start_p, trans_p, "invalid")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: emission_probabilities must be a dict
|
||||
>>> viterbi(observations, states, start_p, trans_p, None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: There's an empty parameter
|
||||
|
||||
"""
|
||||
_validation(
|
||||
|
@ -213,7 +196,6 @@ def _validation(
|
|||
... "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6},
|
||||
... }
|
||||
>>> _validation(observations, states, start_p, trans_p, emit_p)
|
||||
|
||||
>>> _validation([], states, start_p, trans_p, emit_p)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -242,7 +224,6 @@ def _validate_not_empty(
|
|||
"""
|
||||
>>> _validate_not_empty(["a"], ["b"], {"c":0.5},
|
||||
... {"d": {"e": 0.6}}, {"f": {"g": 0.7}})
|
||||
|
||||
>>> _validate_not_empty(["a"], ["b"], {"c":0.5}, {}, {"f": {"g": 0.7}})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -267,12 +248,10 @@ def _validate_not_empty(
|
|||
def _validate_lists(observations_space: Any, states_space: Any) -> None:
|
||||
"""
|
||||
>>> _validate_lists(["a"], ["b"])
|
||||
|
||||
>>> _validate_lists(1234, ["b"])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: observations_space must be a list
|
||||
|
||||
>>> _validate_lists(["a"], [3])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -285,7 +264,6 @@ def _validate_lists(observations_space: Any, states_space: Any) -> None:
|
|||
def _validate_list(_object: Any, var_name: str) -> None:
|
||||
"""
|
||||
>>> _validate_list(["a"], "mock_name")
|
||||
|
||||
>>> _validate_list("a", "mock_name")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -294,7 +272,6 @@ def _validate_list(_object: Any, var_name: str) -> None:
|
|||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: mock_name must be a list of strings
|
||||
|
||||
"""
|
||||
if not isinstance(_object, list):
|
||||
msg = f"{var_name} must be a list"
|
||||
|
@ -313,7 +290,6 @@ def _validate_dicts(
|
|||
) -> None:
|
||||
"""
|
||||
>>> _validate_dicts({"c":0.5}, {"d": {"e": 0.6}}, {"f": {"g": 0.7}})
|
||||
|
||||
>>> _validate_dicts("invalid", {"d": {"e": 0.6}}, {"f": {"g": 0.7}})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -339,7 +315,6 @@ def _validate_dicts(
|
|||
def _validate_nested_dict(_object: Any, var_name: str) -> None:
|
||||
"""
|
||||
>>> _validate_nested_dict({"a":{"b": 0.5}}, "mock_name")
|
||||
|
||||
>>> _validate_nested_dict("invalid", "mock_name")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -367,7 +342,6 @@ def _validate_dict(
|
|||
) -> None:
|
||||
"""
|
||||
>>> _validate_dict({"b": 0.5}, "mock_name", float)
|
||||
|
||||
>>> _validate_dict("invalid", "mock_name", float)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
|
|
@ -21,6 +21,26 @@ def electric_conductivity(
|
|||
('conductivity', 5.12672e-14)
|
||||
>>> electric_conductivity(conductivity=1000, electron_conc=0, mobility=1200)
|
||||
('electron_conc', 5.201506356240767e+18)
|
||||
>>> electric_conductivity(conductivity=-10, electron_conc=100, mobility=0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Conductivity cannot be negative
|
||||
>>> electric_conductivity(conductivity=50, electron_conc=-10, mobility=0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Electron concentration cannot be negative
|
||||
>>> electric_conductivity(conductivity=50, electron_conc=0, mobility=-10)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: mobility cannot be negative
|
||||
>>> electric_conductivity(conductivity=50, electron_conc=0, mobility=0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: You cannot supply more or less than 2 values
|
||||
>>> electric_conductivity(conductivity=50, electron_conc=200, mobility=300)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: You cannot supply more or less than 2 values
|
||||
"""
|
||||
if (conductivity, electron_conc, mobility).count(0) != 1:
|
||||
raise ValueError("You cannot supply more or less than 2 values")
|
||||
|
|
|
@ -23,20 +23,22 @@ def electric_power(voltage: float, current: float, power: float) -> tuple:
|
|||
>>> electric_power(voltage=2, current=4, power=2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Only one argument must be 0
|
||||
ValueError: Exactly one argument must be 0
|
||||
>>> electric_power(voltage=0, current=0, power=2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Only one argument must be 0
|
||||
ValueError: Exactly one argument must be 0
|
||||
>>> electric_power(voltage=0, current=2, power=-4)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Power cannot be negative in any electrical/electronics system
|
||||
>>> electric_power(voltage=2.2, current=2.2, power=0)
|
||||
Result(name='power', value=4.84)
|
||||
>>> electric_power(current=0, power=6, voltage=2)
|
||||
Result(name='current', value=3.0)
|
||||
"""
|
||||
if (voltage, current, power).count(0) != 1:
|
||||
raise ValueError("Only one argument must be 0")
|
||||
raise ValueError("Exactly one argument must be 0")
|
||||
elif power < 0:
|
||||
raise ValueError(
|
||||
"Power cannot be negative in any electrical/electronics system"
|
||||
|
@ -48,7 +50,7 @@ def electric_power(voltage: float, current: float, power: float) -> tuple:
|
|||
elif power == 0:
|
||||
return Result("power", float(round(abs(voltage * current), 2)))
|
||||
else:
|
||||
raise ValueError("Exactly one argument must be 0")
|
||||
raise AssertionError
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -6,7 +6,7 @@ Source: https://en.wikipedia.org/wiki/Electrical_impedance
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from math import pow, sqrt
|
||||
from math import pow, sqrt # noqa: A004
|
||||
|
||||
|
||||
def electrical_impedance(
|
||||
|
|
|
@ -21,10 +21,11 @@ def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl
|
|||
computation like Haversine can be handy for shorter range distances.
|
||||
|
||||
Args:
|
||||
lat1, lon1: latitude and longitude of coordinate 1
|
||||
lat2, lon2: latitude and longitude of coordinate 2
|
||||
* `lat1`, `lon1`: latitude and longitude of coordinate 1
|
||||
* `lat2`, `lon2`: latitude and longitude of coordinate 2
|
||||
Returns:
|
||||
geographical distance between two points in metres
|
||||
|
||||
>>> from collections import namedtuple
|
||||
>>> point_2d = namedtuple("point_2d", "lat lon")
|
||||
>>> SAN_FRANCISCO = point_2d(37.774856, -122.424227)
|
||||
|
|
|
@ -48,6 +48,18 @@ class Side:
|
|||
Side(length=5, angle=Angle(degrees=45.6), next_side=None)
|
||||
>>> Side(5, Angle(45.6), Side(1, Angle(2))) # doctest: +ELLIPSIS
|
||||
Side(length=5, angle=Angle(degrees=45.6), next_side=Side(length=1, angle=Angle(d...
|
||||
>>> Side(-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: length must be a positive numeric value.
|
||||
>>> Side(5, None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: angle must be an Angle object.
|
||||
>>> Side(5, Angle(90), "Invalid next_side")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: next_side must be a Side or None.
|
||||
"""
|
||||
|
||||
length: float
|
||||
|
@ -162,6 +174,19 @@ class Polygon:
|
|||
|
||||
>>> Polygon()
|
||||
Polygon(sides=[])
|
||||
>>> polygon = Polygon()
|
||||
>>> polygon.add_side(Side(5)).get_side(0)
|
||||
Side(length=5, angle=Angle(degrees=90), next_side=None)
|
||||
>>> polygon.get_side(1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
>>> polygon.set_side(0, Side(10)).get_side(0)
|
||||
Side(length=10, angle=Angle(degrees=90), next_side=None)
|
||||
>>> polygon.set_side(1, Side(10))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list assignment index out of range
|
||||
"""
|
||||
|
||||
sides: list[Side] = field(default_factory=list)
|
||||
|
@ -207,6 +232,10 @@ class Rectangle(Polygon):
|
|||
30
|
||||
>>> rectangle_one.area()
|
||||
50
|
||||
>>> Rectangle(-5, 10)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: length must be a positive numeric value.
|
||||
"""
|
||||
|
||||
def __init__(self, short_side_length: float, long_side_length: float) -> None:
|
||||
|
|
46
graphics/butterfly_pattern.py
Normal file
46
graphics/butterfly_pattern.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
def butterfly_pattern(n: int) -> str:
|
||||
"""
|
||||
Creates a butterfly pattern of size n and returns it as a string.
|
||||
|
||||
>>> print(butterfly_pattern(3))
|
||||
* *
|
||||
** **
|
||||
*****
|
||||
** **
|
||||
* *
|
||||
>>> print(butterfly_pattern(5))
|
||||
* *
|
||||
** **
|
||||
*** ***
|
||||
**** ****
|
||||
*********
|
||||
**** ****
|
||||
*** ***
|
||||
** **
|
||||
* *
|
||||
"""
|
||||
result = []
|
||||
|
||||
# Upper part
|
||||
for i in range(1, n):
|
||||
left_stars = "*" * i
|
||||
spaces = " " * (2 * (n - i) - 1)
|
||||
right_stars = "*" * i
|
||||
result.append(left_stars + spaces + right_stars)
|
||||
|
||||
# Middle part
|
||||
result.append("*" * (2 * n - 1))
|
||||
|
||||
# Lower part
|
||||
for i in range(n - 1, 0, -1):
|
||||
left_stars = "*" * i
|
||||
spaces = " " * (2 * (n - i) - 1)
|
||||
right_stars = "*" * i
|
||||
result.append(left_stars + spaces + right_stars)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
n = int(input("Enter the size of the butterfly pattern: "))
|
||||
print(butterfly_pattern(n))
|
52
graphics/digital_differential_analyzer_line.py
Normal file
52
graphics/digital_differential_analyzer_line.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
def digital_differential_analyzer_line(
|
||||
p1: tuple[int, int], p2: tuple[int, int]
|
||||
) -> list[tuple[int, int]]:
|
||||
"""
|
||||
Draws a line between two points using the DDA algorithm.
|
||||
|
||||
Args:
|
||||
- p1: Coordinates of the starting point.
|
||||
- p2: Coordinates of the ending point.
|
||||
Returns:
|
||||
- List of coordinate points that form the line.
|
||||
|
||||
>>> digital_differential_analyzer_line((1, 1), (4, 4))
|
||||
[(2, 2), (3, 3), (4, 4)]
|
||||
"""
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
dx = x2 - x1
|
||||
dy = y2 - y1
|
||||
steps = max(abs(dx), abs(dy))
|
||||
x_increment = dx / float(steps)
|
||||
y_increment = dy / float(steps)
|
||||
coordinates = []
|
||||
x: float = x1
|
||||
y: float = y1
|
||||
for _ in range(steps):
|
||||
x += x_increment
|
||||
y += y_increment
|
||||
coordinates.append((int(round(x)), int(round(y))))
|
||||
return coordinates
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
x1 = int(input("Enter the x-coordinate of the starting point: "))
|
||||
y1 = int(input("Enter the y-coordinate of the starting point: "))
|
||||
x2 = int(input("Enter the x-coordinate of the ending point: "))
|
||||
y2 = int(input("Enter the y-coordinate of the ending point: "))
|
||||
coordinates = digital_differential_analyzer_line((x1, y1), (x2, y2))
|
||||
x_points, y_points = zip(*coordinates)
|
||||
plt.plot(x_points, y_points, marker="o")
|
||||
plt.title("Digital Differential Analyzer Line Drawing Algorithm")
|
||||
plt.xlabel("X-axis")
|
||||
plt.ylabel("Y-axis")
|
||||
plt.grid()
|
||||
plt.show()
|
|
@ -194,10 +194,8 @@ def city_select(
|
|||
IndexError: list index out of range
|
||||
"""
|
||||
probabilities = []
|
||||
for city in unvisited_cities:
|
||||
city_distance = distance(
|
||||
unvisited_cities[city], next(iter(current_city.values()))
|
||||
)
|
||||
for city, value in unvisited_cities.items():
|
||||
city_distance = distance(value, next(iter(current_city.values())))
|
||||
probability = (pheromone[city][next(iter(current_city.keys()))] ** alpha) * (
|
||||
(1 / city_distance) ** beta
|
||||
)
|
||||
|
|
|
@ -77,6 +77,14 @@ if __name__ == "__main__":
|
|||
|
||||
|
||||
def dfs(g, s):
|
||||
"""
|
||||
>>> dfs({1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []}, 1)
|
||||
1
|
||||
2
|
||||
4
|
||||
5
|
||||
3
|
||||
"""
|
||||
vis, _s = {s}, [s]
|
||||
print(s)
|
||||
while _s:
|
||||
|
@ -104,6 +112,17 @@ def dfs(g, s):
|
|||
|
||||
|
||||
def bfs(g, s):
|
||||
"""
|
||||
>>> bfs({1: [2, 3], 2: [4, 5], 3: [6, 7], 4: [], 5: [8], 6: [], 7: [], 8: []}, 1)
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
"""
|
||||
vis, q = {s}, deque([s])
|
||||
print(s)
|
||||
while q:
|
||||
|
@ -128,23 +147,36 @@ def bfs(g, s):
|
|||
|
||||
|
||||
def dijk(g, s):
|
||||
"""
|
||||
dijk({1: [(2, 7), (3, 9), (6, 14)],
|
||||
2: [(1, 7), (3, 10), (4, 15)],
|
||||
3: [(1, 9), (2, 10), (4, 11), (6, 2)],
|
||||
4: [(2, 15), (3, 11), (5, 6)],
|
||||
5: [(4, 6), (6, 9)],
|
||||
6: [(1, 14), (3, 2), (5, 9)]}, 1)
|
||||
7
|
||||
9
|
||||
11
|
||||
20
|
||||
20
|
||||
"""
|
||||
dist, known, path = {s: 0}, set(), {s: 0}
|
||||
while True:
|
||||
if len(known) == len(g) - 1:
|
||||
break
|
||||
mini = 100000
|
||||
for i in dist:
|
||||
if i not in known and dist[i] < mini:
|
||||
mini = dist[i]
|
||||
u = i
|
||||
for key, value in dist:
|
||||
if key not in known and value < mini:
|
||||
mini = value
|
||||
u = key
|
||||
known.add(u)
|
||||
for v in g[u]:
|
||||
if v[0] not in known and dist[u] + v[1] < dist.get(v[0], 100000):
|
||||
dist[v[0]] = dist[u] + v[1]
|
||||
path[v[0]] = u
|
||||
for i in dist:
|
||||
if i != s:
|
||||
print(dist[i])
|
||||
for key, value in dist.items():
|
||||
if key != s:
|
||||
print(value)
|
||||
|
||||
|
||||
"""
|
||||
|
@ -255,10 +287,10 @@ def prim(g, s):
|
|||
if len(known) == len(g) - 1:
|
||||
break
|
||||
mini = 100000
|
||||
for i in dist:
|
||||
if i not in known and dist[i] < mini:
|
||||
mini = dist[i]
|
||||
u = i
|
||||
for key, value in dist.items():
|
||||
if key not in known and value < mini:
|
||||
mini = value
|
||||
u = key
|
||||
known.add(u)
|
||||
for v in g[u]:
|
||||
if v[0] not in known and v[1] < dist.get(v[0], 100000):
|
||||
|
|
|
@ -6,16 +6,17 @@ def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool:
|
|||
Check if a graph is bipartite using depth-first search (DFS).
|
||||
|
||||
Args:
|
||||
graph: Adjacency list representing the graph.
|
||||
`graph`: Adjacency list representing the graph.
|
||||
|
||||
Returns:
|
||||
True if bipartite, False otherwise.
|
||||
``True`` if bipartite, ``False`` otherwise.
|
||||
|
||||
Checks if the graph can be divided into two sets of vertices, such that no two
|
||||
vertices within the same set are connected by an edge.
|
||||
|
||||
Examples:
|
||||
# FIXME: This test should pass.
|
||||
|
||||
>>> # FIXME: This test should pass.
|
||||
>>> is_bipartite_dfs(defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4]}))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -37,7 +38,7 @@ def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool:
|
|||
...
|
||||
KeyError: 0
|
||||
|
||||
# FIXME: This test should fails with KeyError: 4.
|
||||
>>> # FIXME: This test should fails with KeyError: 4.
|
||||
>>> is_bipartite_dfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]})
|
||||
False
|
||||
>>> is_bipartite_dfs({0: [-1, 3], 1: [0, -2]})
|
||||
|
@ -51,7 +52,8 @@ def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool:
|
|||
...
|
||||
KeyError: 0
|
||||
|
||||
# FIXME: This test should fails with TypeError: list indices must be integers or...
|
||||
>>> # FIXME: This test should fails with
|
||||
>>> # TypeError: list indices must be integers or...
|
||||
>>> is_bipartite_dfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]})
|
||||
True
|
||||
>>> is_bipartite_dfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]})
|
||||
|
@ -95,16 +97,17 @@ def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool:
|
|||
Check if a graph is bipartite using a breadth-first search (BFS).
|
||||
|
||||
Args:
|
||||
graph: Adjacency list representing the graph.
|
||||
`graph`: Adjacency list representing the graph.
|
||||
|
||||
Returns:
|
||||
True if bipartite, False otherwise.
|
||||
``True`` if bipartite, ``False`` otherwise.
|
||||
|
||||
Check if the graph can be divided into two sets of vertices, such that no two
|
||||
vertices within the same set are connected by an edge.
|
||||
|
||||
Examples:
|
||||
# FIXME: This test should pass.
|
||||
|
||||
>>> # FIXME: This test should pass.
|
||||
>>> is_bipartite_bfs(defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4]}))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -126,7 +129,7 @@ def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool:
|
|||
...
|
||||
KeyError: 0
|
||||
|
||||
# FIXME: This test should fails with KeyError: 4.
|
||||
>>> # FIXME: This test should fails with KeyError: 4.
|
||||
>>> is_bipartite_bfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]})
|
||||
False
|
||||
>>> is_bipartite_bfs({0: [-1, 3], 1: [0, -2]})
|
||||
|
@ -140,7 +143,8 @@ def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool:
|
|||
...
|
||||
KeyError: 0
|
||||
|
||||
# FIXME: This test should fails with TypeError: list indices must be integers or...
|
||||
>>> # FIXME: This test should fails with
|
||||
>>> # TypeError: list indices must be integers or...
|
||||
>>> is_bipartite_bfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]})
|
||||
True
|
||||
>>> is_bipartite_bfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]})
|
||||
|
|
206
graphs/lanczos_eigenvectors.py
Normal file
206
graphs/lanczos_eigenvectors.py
Normal file
|
@ -0,0 +1,206 @@
|
|||
"""
|
||||
Lanczos Method for Finding Eigenvalues and Eigenvectors of a Graph.
|
||||
|
||||
This module demonstrates the Lanczos method to approximate the largest eigenvalues
|
||||
and corresponding eigenvectors of a symmetric matrix represented as a graph's
|
||||
adjacency list. The method efficiently handles large, sparse matrices by converting
|
||||
the graph to a tridiagonal matrix, whose eigenvalues and eigenvectors are then
|
||||
computed.
|
||||
|
||||
Key Functions:
|
||||
- `find_lanczos_eigenvectors`: Computes the k largest eigenvalues and vectors.
|
||||
- `lanczos_iteration`: Constructs the tridiagonal matrix and orthonormal basis vectors.
|
||||
- `multiply_matrix_vector`: Multiplies an adjacency list graph with a vector.
|
||||
|
||||
Complexity:
|
||||
- Time: O(k * n), where k is the number of eigenvalues and n is the matrix size.
|
||||
- Space: O(n), due to sparse representation and tridiagonal matrix structure.
|
||||
|
||||
Further Reading:
|
||||
- Lanczos Algorithm: https://en.wikipedia.org/wiki/Lanczos_algorithm
|
||||
- Eigenvector Centrality: https://en.wikipedia.org/wiki/Eigenvector_centrality
|
||||
|
||||
Example Usage:
|
||||
Given a graph represented by an adjacency list, the `find_lanczos_eigenvectors`
|
||||
function returns the largest eigenvalues and eigenvectors. This can be used to
|
||||
analyze graph centrality.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def validate_adjacency_list(graph: list[list[int | None]]) -> None:
|
||||
"""Validates the adjacency list format for the graph.
|
||||
|
||||
Args:
|
||||
graph: A list of lists where each sublist contains the neighbors of a node.
|
||||
|
||||
Raises:
|
||||
ValueError: If the graph is not a list of lists, or if any node has
|
||||
invalid neighbors (e.g., out-of-range or non-integer values).
|
||||
|
||||
>>> validate_adjacency_list([[1, 2], [0], [0, 1]])
|
||||
>>> validate_adjacency_list([[]]) # No neighbors, valid case
|
||||
>>> validate_adjacency_list([[1], [2], [-1]]) # Invalid neighbor
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid neighbor -1 in node 2 adjacency list.
|
||||
"""
|
||||
if not isinstance(graph, list):
|
||||
raise ValueError("Graph should be a list of lists.")
|
||||
|
||||
for node_index, neighbors in enumerate(graph):
|
||||
if not isinstance(neighbors, list):
|
||||
no_neighbors_message: str = (
|
||||
f"Node {node_index} should have a list of neighbors."
|
||||
)
|
||||
raise ValueError(no_neighbors_message)
|
||||
for neighbor_index in neighbors:
|
||||
if (
|
||||
not isinstance(neighbor_index, int)
|
||||
or neighbor_index < 0
|
||||
or neighbor_index >= len(graph)
|
||||
):
|
||||
invalid_neighbor_message: str = (
|
||||
f"Invalid neighbor {neighbor_index} in node {node_index} "
|
||||
f"adjacency list."
|
||||
)
|
||||
raise ValueError(invalid_neighbor_message)
|
||||
|
||||
|
||||
def lanczos_iteration(
|
||||
graph: list[list[int | None]], num_eigenvectors: int
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Constructs the tridiagonal matrix and orthonormal basis vectors using the
|
||||
Lanczos method.
|
||||
|
||||
Args:
|
||||
graph: The graph represented as a list of adjacency lists.
|
||||
num_eigenvectors: The number of largest eigenvalues and eigenvectors
|
||||
to approximate.
|
||||
|
||||
Returns:
|
||||
A tuple containing:
|
||||
- tridiagonal_matrix: A (num_eigenvectors x num_eigenvectors) symmetric
|
||||
matrix.
|
||||
- orthonormal_basis: A (num_nodes x num_eigenvectors) matrix of orthonormal
|
||||
basis vectors.
|
||||
|
||||
Raises:
|
||||
ValueError: If num_eigenvectors is less than 1 or greater than the number of
|
||||
nodes.
|
||||
|
||||
>>> graph = [[1, 2], [0, 2], [0, 1]]
|
||||
>>> T, Q = lanczos_iteration(graph, 2)
|
||||
>>> T.shape == (2, 2) and Q.shape == (3, 2)
|
||||
True
|
||||
"""
|
||||
num_nodes: int = len(graph)
|
||||
if not (1 <= num_eigenvectors <= num_nodes):
|
||||
raise ValueError(
|
||||
"Number of eigenvectors must be between 1 and the number of "
|
||||
"nodes in the graph."
|
||||
)
|
||||
|
||||
orthonormal_basis: np.ndarray = np.zeros((num_nodes, num_eigenvectors))
|
||||
tridiagonal_matrix: np.ndarray = np.zeros((num_eigenvectors, num_eigenvectors))
|
||||
|
||||
rng = np.random.default_rng()
|
||||
initial_vector: np.ndarray = rng.random(num_nodes)
|
||||
initial_vector /= np.sqrt(np.dot(initial_vector, initial_vector))
|
||||
orthonormal_basis[:, 0] = initial_vector
|
||||
|
||||
prev_beta: float = 0.0
|
||||
for iter_index in range(num_eigenvectors):
|
||||
result_vector: np.ndarray = multiply_matrix_vector(
|
||||
graph, orthonormal_basis[:, iter_index]
|
||||
)
|
||||
if iter_index > 0:
|
||||
result_vector -= prev_beta * orthonormal_basis[:, iter_index - 1]
|
||||
alpha_value: float = np.dot(orthonormal_basis[:, iter_index], result_vector)
|
||||
result_vector -= alpha_value * orthonormal_basis[:, iter_index]
|
||||
|
||||
prev_beta = np.sqrt(np.dot(result_vector, result_vector))
|
||||
if iter_index < num_eigenvectors - 1 and prev_beta > 1e-10:
|
||||
orthonormal_basis[:, iter_index + 1] = result_vector / prev_beta
|
||||
tridiagonal_matrix[iter_index, iter_index] = alpha_value
|
||||
if iter_index < num_eigenvectors - 1:
|
||||
tridiagonal_matrix[iter_index, iter_index + 1] = prev_beta
|
||||
tridiagonal_matrix[iter_index + 1, iter_index] = prev_beta
|
||||
return tridiagonal_matrix, orthonormal_basis
|
||||
|
||||
|
||||
def multiply_matrix_vector(
|
||||
graph: list[list[int | None]], vector: np.ndarray
|
||||
) -> np.ndarray:
|
||||
"""Performs multiplication of a graph's adjacency list representation with a vector.
|
||||
|
||||
Args:
|
||||
graph: The adjacency list of the graph.
|
||||
vector: A 1D numpy array representing the vector to multiply.
|
||||
|
||||
Returns:
|
||||
A numpy array representing the product of the adjacency list and the vector.
|
||||
|
||||
Raises:
|
||||
ValueError: If the vector's length does not match the number of nodes in the
|
||||
graph.
|
||||
|
||||
>>> multiply_matrix_vector([[1, 2], [0, 2], [0, 1]], np.array([1, 1, 1]))
|
||||
array([2., 2., 2.])
|
||||
>>> multiply_matrix_vector([[1, 2], [0, 2], [0, 1]], np.array([0, 1, 0]))
|
||||
array([1., 0., 1.])
|
||||
"""
|
||||
num_nodes: int = len(graph)
|
||||
if vector.shape[0] != num_nodes:
|
||||
raise ValueError("Vector length must match the number of nodes in the graph.")
|
||||
|
||||
result: np.ndarray = np.zeros(num_nodes)
|
||||
for node_index, neighbors in enumerate(graph):
|
||||
for neighbor_index in neighbors:
|
||||
result[node_index] += vector[neighbor_index]
|
||||
return result
|
||||
|
||||
|
||||
def find_lanczos_eigenvectors(
|
||||
graph: list[list[int | None]], num_eigenvectors: int
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Computes the largest eigenvalues and their corresponding eigenvectors using the
|
||||
Lanczos method.
|
||||
|
||||
Args:
|
||||
graph: The graph as a list of adjacency lists.
|
||||
num_eigenvectors: Number of largest eigenvalues and eigenvectors to compute.
|
||||
|
||||
Returns:
|
||||
A tuple containing:
|
||||
- eigenvalues: 1D array of the largest eigenvalues in descending order.
|
||||
- eigenvectors: 2D array where each column is an eigenvector corresponding
|
||||
to an eigenvalue.
|
||||
|
||||
Raises:
|
||||
ValueError: If the graph format is invalid or num_eigenvectors is out of bounds.
|
||||
|
||||
>>> eigenvalues, eigenvectors = find_lanczos_eigenvectors(
|
||||
... [[1, 2], [0, 2], [0, 1]], 2
|
||||
... )
|
||||
>>> len(eigenvalues) == 2 and eigenvectors.shape[1] == 2
|
||||
True
|
||||
"""
|
||||
validate_adjacency_list(graph)
|
||||
tridiagonal_matrix, orthonormal_basis = lanczos_iteration(graph, num_eigenvectors)
|
||||
eigenvalues, eigenvectors = np.linalg.eigh(tridiagonal_matrix)
|
||||
return eigenvalues[::-1], np.dot(orthonormal_basis, eigenvectors[:, ::-1])
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Main driver function for testing the implementation with doctests.
|
||||
"""
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -185,12 +185,12 @@ class Graph:
|
|||
|
||||
if cheap_edge[set2] == -1 or cheap_edge[set2][2] > weight:
|
||||
cheap_edge[set2] = [head, tail, weight]
|
||||
for vertex in cheap_edge:
|
||||
if cheap_edge[vertex] != -1:
|
||||
head, tail, weight = cheap_edge[vertex]
|
||||
for head_tail_weight in cheap_edge.values():
|
||||
if head_tail_weight != -1:
|
||||
head, tail, weight = head_tail_weight
|
||||
if union_find.find(head) != union_find.find(tail):
|
||||
union_find.union(head, tail)
|
||||
mst_edges.append(cheap_edge[vertex])
|
||||
mst_edges.append(head_tail_weight)
|
||||
num_components = num_components - 1
|
||||
mst = Graph.build(edges=mst_edges)
|
||||
return mst
|
||||
|
|
|
@ -14,12 +14,13 @@ def smallest_range(nums: list[list[int]]) -> list[int]:
|
|||
Uses min heap for efficiency. The range includes at least one number from each list.
|
||||
|
||||
Args:
|
||||
nums: List of k sorted integer lists.
|
||||
`nums`: List of k sorted integer lists.
|
||||
|
||||
Returns:
|
||||
list: Smallest range as a two-element list.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> smallest_range([[4, 10, 15, 24, 26], [0, 9, 12, 20], [5, 18, 22, 30]])
|
||||
[20, 24]
|
||||
>>> smallest_range([[1, 2, 3], [1, 2, 3], [1, 2, 3]])
|
||||
|
|
|
@ -131,7 +131,7 @@ def preprocess(message: bytes) -> bytes:
|
|||
return bit_string
|
||||
|
||||
|
||||
def get_block_words(bit_string: bytes) -> Generator[list[int], None, None]:
|
||||
def get_block_words(bit_string: bytes) -> Generator[list[int]]:
|
||||
"""
|
||||
Splits bit string into blocks of 512 chars and yields each block as a list
|
||||
of 32-bit words
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""
|
||||
Gaussian elimination method for solving a system of linear equations.
|
||||
Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_elimination
|
||||
| Gaussian elimination method for solving a system of linear equations.
|
||||
| Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_elimination
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
@ -13,12 +13,17 @@ def retroactive_resolution(
|
|||
) -> NDArray[float64]:
|
||||
"""
|
||||
This function performs a retroactive linear system resolution
|
||||
for triangular matrix
|
||||
for triangular matrix
|
||||
|
||||
Examples:
|
||||
2x1 + 2x2 - 1x3 = 5 2x1 + 2x2 = -1
|
||||
0x1 - 2x2 - 1x3 = -7 0x1 - 2x2 = -1
|
||||
0x1 + 0x2 + 5x3 = 15
|
||||
1.
|
||||
* 2x1 + 2x2 - 1x3 = 5
|
||||
* 0x1 - 2x2 - 1x3 = -7
|
||||
* 0x1 + 0x2 + 5x3 = 15
|
||||
2.
|
||||
* 2x1 + 2x2 = -1
|
||||
* 0x1 - 2x2 = -1
|
||||
|
||||
>>> gaussian_elimination([[2, 2, -1], [0, -2, -1], [0, 0, 5]], [[5], [-7], [15]])
|
||||
array([[2.],
|
||||
[2.],
|
||||
|
@ -45,9 +50,14 @@ def gaussian_elimination(
|
|||
This function performs Gaussian elimination method
|
||||
|
||||
Examples:
|
||||
1x1 - 4x2 - 2x3 = -2 1x1 + 2x2 = 5
|
||||
5x1 + 2x2 - 2x3 = -3 5x1 + 2x2 = 5
|
||||
1x1 - 1x2 + 0x3 = 4
|
||||
1.
|
||||
* 1x1 - 4x2 - 2x3 = -2
|
||||
* 5x1 + 2x2 - 2x3 = -3
|
||||
* 1x1 - 1x2 + 0x3 = 4
|
||||
2.
|
||||
* 1x1 + 2x2 = 5
|
||||
* 5x1 + 2x2 = 5
|
||||
|
||||
>>> gaussian_elimination([[1, -4, -2], [5, 2, -2], [1, -1, 0]], [[-2], [-3], [4]])
|
||||
array([[ 2.3 ],
|
||||
[-1.7 ],
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
Lower-upper (LU) decomposition factors a matrix as a product of a lower
|
||||
triangular matrix and an upper triangular matrix. A square matrix has an LU
|
||||
decomposition under the following conditions:
|
||||
|
||||
- If the matrix is invertible, then it has an LU decomposition if and only
|
||||
if all of its leading principal minors are non-zero (see
|
||||
https://en.wikipedia.org/wiki/Minor_(linear_algebra) for an explanation of
|
||||
leading principal minors of a matrix).
|
||||
if all of its leading principal minors are non-zero (see
|
||||
https://en.wikipedia.org/wiki/Minor_(linear_algebra) for an explanation of
|
||||
leading principal minors of a matrix).
|
||||
- If the matrix is singular (i.e., not invertible) and it has a rank of k
|
||||
(i.e., it has k linearly independent columns), then it has an LU
|
||||
decomposition if its first k leading principal minors are non-zero.
|
||||
(i.e., it has k linearly independent columns), then it has an LU
|
||||
decomposition if its first k leading principal minors are non-zero.
|
||||
|
||||
This algorithm will simply attempt to perform LU decomposition on any square
|
||||
matrix and raise an error if no such decomposition exists.
|
||||
|
@ -25,6 +26,7 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray
|
|||
"""
|
||||
Perform LU decomposition on a given matrix and raises an error if the matrix
|
||||
isn't square or if no such decomposition exists
|
||||
|
||||
>>> matrix = np.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]])
|
||||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||||
>>> lower_mat
|
||||
|
@ -45,7 +47,7 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray
|
|||
array([[ 4. , 3. ],
|
||||
[ 0. , -1.5]])
|
||||
|
||||
# Matrix is not square
|
||||
>>> # Matrix is not square
|
||||
>>> matrix = np.array([[2, -2, 1], [0, 1, 2]])
|
||||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||||
Traceback (most recent call last):
|
||||
|
@ -54,14 +56,14 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray
|
|||
[[ 2 -2 1]
|
||||
[ 0 1 2]]
|
||||
|
||||
# Matrix is invertible, but its first leading principal minor is 0
|
||||
>>> # Matrix is invertible, but its first leading principal minor is 0
|
||||
>>> matrix = np.array([[0, 1], [1, 0]])
|
||||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ArithmeticError: No LU decomposition exists
|
||||
|
||||
# Matrix is singular, but its first leading principal minor is 1
|
||||
>>> # Matrix is singular, but its first leading principal minor is 1
|
||||
>>> matrix = np.array([[1, 0], [1, 0]])
|
||||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||||
>>> lower_mat
|
||||
|
@ -71,7 +73,7 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray
|
|||
array([[1., 0.],
|
||||
[0., 0.]])
|
||||
|
||||
# Matrix is singular, but its first leading principal minor is 0
|
||||
>>> # Matrix is singular, but its first leading principal minor is 0
|
||||
>>> matrix = np.array([[0, 1], [0, 1]])
|
||||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||||
Traceback (most recent call last):
|
||||
|
|
|
@ -6,56 +6,50 @@ def solve_linear_system(matrix: np.ndarray) -> np.ndarray:
|
|||
Solve a linear system of equations using Gaussian elimination with partial pivoting
|
||||
|
||||
Args:
|
||||
- matrix: Coefficient matrix with the last column representing the constants.
|
||||
- `matrix`: Coefficient matrix with the last column representing the constants.
|
||||
|
||||
Returns:
|
||||
- Solution vector.
|
||||
- Solution vector.
|
||||
|
||||
Raises:
|
||||
- ValueError: If the matrix is not correct (i.e., singular).
|
||||
- ``ValueError``: If the matrix is not correct (i.e., singular).
|
||||
|
||||
https://courses.engr.illinois.edu/cs357/su2013/lect.htm Lecture 7
|
||||
|
||||
Example:
|
||||
|
||||
>>> A = np.array([[2, 1, -1], [-3, -1, 2], [-2, 1, 2]], dtype=float)
|
||||
>>> B = np.array([8, -11, -3], dtype=float)
|
||||
>>> solution = solve_linear_system(np.column_stack((A, B)))
|
||||
>>> np.allclose(solution, np.array([2., 3., -1.]))
|
||||
True
|
||||
>>> solve_linear_system(np.array([[0, 0], [0, 0]], dtype=float))
|
||||
array([nan, nan])
|
||||
>>> solve_linear_system(np.array([[0, 0, 0]], dtype=float))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Matrix is not square
|
||||
>>> solve_linear_system(np.array([[0, 0, 0], [0, 0, 0]], dtype=float))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Matrix is singular
|
||||
"""
|
||||
ab = np.copy(matrix)
|
||||
num_of_rows = ab.shape[0]
|
||||
num_of_columns = ab.shape[1] - 1
|
||||
x_lst: list[float] = []
|
||||
|
||||
# Lead element search
|
||||
for column_num in range(num_of_rows):
|
||||
for i in range(column_num, num_of_columns):
|
||||
if abs(ab[i][column_num]) > abs(ab[column_num][column_num]):
|
||||
ab[[column_num, i]] = ab[[i, column_num]]
|
||||
if ab[column_num, column_num] == 0.0:
|
||||
raise ValueError("Matrix is not correct")
|
||||
else:
|
||||
pass
|
||||
if column_num != 0:
|
||||
for i in range(column_num, num_of_rows):
|
||||
ab[i, :] -= (
|
||||
ab[i, column_num - 1]
|
||||
/ ab[column_num - 1, column_num - 1]
|
||||
* ab[column_num - 1, :]
|
||||
)
|
||||
if num_of_rows != num_of_columns:
|
||||
raise ValueError("Matrix is not square")
|
||||
|
||||
# Upper triangular matrix
|
||||
for column_num in range(num_of_rows):
|
||||
# Lead element search
|
||||
for i in range(column_num, num_of_columns):
|
||||
if abs(ab[i][column_num]) > abs(ab[column_num][column_num]):
|
||||
ab[[column_num, i]] = ab[[i, column_num]]
|
||||
if ab[column_num, column_num] == 0.0:
|
||||
raise ValueError("Matrix is not correct")
|
||||
else:
|
||||
pass
|
||||
|
||||
# Upper triangular matrix
|
||||
if abs(ab[column_num, column_num]) < 1e-8:
|
||||
raise ValueError("Matrix is singular")
|
||||
|
||||
if column_num != 0:
|
||||
for i in range(column_num, num_of_rows):
|
||||
ab[i, :] -= (
|
||||
|
|
|
@ -46,7 +46,6 @@ class Vector:
|
|||
change_component(pos: int, value: float): changes specified component
|
||||
euclidean_length(): returns the euclidean length of the vector
|
||||
angle(other: Vector, deg: bool): returns the angle between two vectors
|
||||
TODO: compare-operator
|
||||
"""
|
||||
|
||||
def __init__(self, components: Collection[float] | None = None) -> None:
|
||||
|
@ -96,6 +95,16 @@ class Vector:
|
|||
else: # error case
|
||||
raise Exception("must have the same size")
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""
|
||||
performs the comparison between two vectors
|
||||
"""
|
||||
if not isinstance(other, Vector):
|
||||
return NotImplemented
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
return all(self.component(i) == other.component(i) for i in range(len(self)))
|
||||
|
||||
@overload
|
||||
def __mul__(self, other: float) -> Vector: ...
|
||||
|
||||
|
|
|
@ -8,11 +8,15 @@ See: https://en.wikipedia.org/wiki/Rank_(linear_algebra)
|
|||
def rank_of_matrix(matrix: list[list[int | float]]) -> int:
|
||||
"""
|
||||
Finds the rank of a matrix.
|
||||
|
||||
Args:
|
||||
matrix: The matrix as a list of lists.
|
||||
`matrix`: The matrix as a list of lists.
|
||||
|
||||
Returns:
|
||||
The rank of the matrix.
|
||||
|
||||
Example:
|
||||
|
||||
>>> matrix1 = [[1, 2, 3],
|
||||
... [4, 5, 6],
|
||||
... [7, 8, 9]]
|
||||
|
|
|
@ -12,13 +12,14 @@ def schur_complement(
|
|||
) -> np.ndarray:
|
||||
"""
|
||||
Schur complement of a symmetric matrix X given as a 2x2 block matrix
|
||||
consisting of matrices A, B and C.
|
||||
Matrix A must be quadratic and non-singular.
|
||||
In case A is singular, a pseudo-inverse may be provided using
|
||||
the pseudo_inv argument.
|
||||
consisting of matrices `A`, `B` and `C`.
|
||||
Matrix `A` must be quadratic and non-singular.
|
||||
In case `A` is singular, a pseudo-inverse may be provided using
|
||||
the `pseudo_inv` argument.
|
||||
|
||||
| Link to Wiki: https://en.wikipedia.org/wiki/Schur_complement
|
||||
| See also Convex Optimization - Boyd and Vandenberghe, A.5.5
|
||||
|
||||
Link to Wiki: https://en.wikipedia.org/wiki/Schur_complement
|
||||
See also Convex Optimization - Boyd and Vandenberghe, A.5.5
|
||||
>>> import numpy as np
|
||||
>>> a = np.array([[1, 2], [2, 1]])
|
||||
>>> b = np.array([[0, 3], [3, 0]])
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
|
||||
I have added the codes for reflection, projection, scaling and rotation 2D matrices.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
scaling(5) = [[5.0, 0.0], [0.0, 5.0]]
|
||||
rotation(45) = [[0.5253219888177297, -0.8509035245341184],
|
||||
[0.8509035245341184, 0.5253219888177297]]
|
||||
projection(45) = [[0.27596319193541496, 0.446998331800279],
|
||||
[0.446998331800279, 0.7240368080645851]]
|
||||
reflection(45) = [[0.05064397763545947, 0.893996663600558],
|
||||
[0.893996663600558, 0.7018070490682369]]
|
||||
rotation(45) = [[0.5253219888177297, -0.8509035245341184],
|
||||
[0.8509035245341184, 0.5253219888177297]]
|
||||
projection(45) = [[0.27596319193541496, 0.446998331800279],
|
||||
[0.446998331800279, 0.7240368080645851]]
|
||||
reflection(45) = [[0.05064397763545947, 0.893996663600558],
|
||||
[0.893996663600558, 0.7018070490682369]]
|
||||
"""
|
||||
|
||||
from math import cos, sin
|
||||
|
|
|
@ -107,8 +107,8 @@ def create_tree(data_set: list, min_sup: int = 1) -> tuple[TreeNode, dict]:
|
|||
if not (freq_item_set := set(header_table)):
|
||||
return TreeNode("Null Set", 1, None), {}
|
||||
|
||||
for k in header_table:
|
||||
header_table[k] = [header_table[k], None]
|
||||
for key, value in header_table.items():
|
||||
header_table[key] = [value, None]
|
||||
|
||||
fp_tree = TreeNode("Null Set", 1, None) # Parent is None for the root node
|
||||
for tran_set in data_set:
|
||||
|
|
|
@ -322,7 +322,7 @@ def main():
|
|||
user_count = valid_input(
|
||||
input_type=int,
|
||||
condition=lambda x: x > 0,
|
||||
input_msg=(f"Enter The number of instances for class_{i+1}: "),
|
||||
input_msg=(f"Enter The number of instances for class_{i + 1}: "),
|
||||
err_msg="Number of instances should be positive!",
|
||||
)
|
||||
counts.append(user_count)
|
||||
|
@ -333,7 +333,7 @@ def main():
|
|||
for a in range(n_classes):
|
||||
user_mean = valid_input(
|
||||
input_type=float,
|
||||
input_msg=(f"Enter the value of mean for class_{a+1}: "),
|
||||
input_msg=(f"Enter the value of mean for class_{a + 1}: "),
|
||||
err_msg="This is an invalid value.",
|
||||
)
|
||||
user_means.append(user_mean)
|
||||
|
|
|
@ -41,6 +41,14 @@ def run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta):
|
|||
:param theta : Feature vector (weight's for our model)
|
||||
;param return : Updated Feature's, using
|
||||
curr_features - alpha_ * gradient(w.r.t. feature)
|
||||
>>> import numpy as np
|
||||
>>> data_x = np.array([[1, 2], [3, 4]])
|
||||
>>> data_y = np.array([5, 6])
|
||||
>>> len_data = len(data_x)
|
||||
>>> alpha = 0.01
|
||||
>>> theta = np.array([0.1, 0.2])
|
||||
>>> run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta)
|
||||
array([0.196, 0.343])
|
||||
"""
|
||||
n = len_data
|
||||
|
||||
|
@ -58,6 +66,12 @@ def sum_of_square_error(data_x, data_y, len_data, theta):
|
|||
:param len_data : len of the dataset
|
||||
:param theta : contains the feature vector
|
||||
:return : sum of square error computed from given feature's
|
||||
|
||||
Example:
|
||||
>>> vc_x = np.array([[1.1], [2.1], [3.1]])
|
||||
>>> vc_y = np.array([1.2, 2.2, 3.2])
|
||||
>>> round(sum_of_square_error(vc_x, vc_y, 3, np.array([1])),3)
|
||||
np.float64(0.005)
|
||||
"""
|
||||
prod = np.dot(theta, data_x.transpose())
|
||||
prod -= data_y.transpose()
|
||||
|
@ -93,6 +107,11 @@ def mean_absolute_error(predicted_y, original_y):
|
|||
:param predicted_y : contains the output of prediction (result vector)
|
||||
:param original_y : contains values of expected outcome
|
||||
:return : mean absolute error computed from given feature's
|
||||
|
||||
>>> predicted_y = [3, -0.5, 2, 7]
|
||||
>>> original_y = [2.5, 0.0, 2, 8]
|
||||
>>> mean_absolute_error(predicted_y, original_y)
|
||||
0.5
|
||||
"""
|
||||
total = sum(abs(y - predicted_y[i]) for i, y in enumerate(original_y))
|
||||
return total / len(original_y)
|
||||
|
@ -114,4 +133,7 @@ def main():
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
main()
|
||||
|
|
|
@ -17,7 +17,7 @@ from __future__ import annotations
|
|||
from collections.abc import Generator
|
||||
|
||||
|
||||
def collatz_sequence(n: int) -> Generator[int, None, None]:
|
||||
def collatz_sequence(n: int) -> Generator[int]:
|
||||
"""
|
||||
Generate the Collatz sequence starting at n.
|
||||
>>> tuple(collatz_sequence(2.1))
|
||||
|
|
|
@ -17,10 +17,8 @@ class Dual:
|
|||
self.duals = rank
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"{self.real}+"
|
||||
f"{'+'.join(str(dual)+'E'+str(n+1)for n,dual in enumerate(self.duals))}"
|
||||
)
|
||||
s = "+".join(f"{dual}E{n}" for n, dual in enumerate(self.duals, 1))
|
||||
return f"{self.real}+{s}"
|
||||
|
||||
def reduce(self):
|
||||
cur = self.duals.copy()
|
||||
|
|
55
maths/geometric_mean.py
Normal file
55
maths/geometric_mean.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
The Geometric Mean of n numbers is defined as the n-th root of the product
|
||||
of those numbers. It is used to measure the central tendency of the numbers.
|
||||
https://en.wikipedia.org/wiki/Geometric_mean
|
||||
"""
|
||||
|
||||
|
||||
def compute_geometric_mean(*args: int) -> float:
|
||||
"""
|
||||
Return the geometric mean of the argument numbers.
|
||||
>>> compute_geometric_mean(2,8)
|
||||
4.0
|
||||
>>> compute_geometric_mean('a', 4)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Not a Number
|
||||
>>> compute_geometric_mean(5, 125)
|
||||
25.0
|
||||
>>> compute_geometric_mean(1, 0)
|
||||
0.0
|
||||
>>> compute_geometric_mean(1, 5, 25, 5)
|
||||
5.0
|
||||
>>> compute_geometric_mean(2, -2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ArithmeticError: Cannot Compute Geometric Mean for these numbers.
|
||||
>>> compute_geometric_mean(-5, 25, 1)
|
||||
-5.0
|
||||
"""
|
||||
product = 1
|
||||
for number in args:
|
||||
if not isinstance(number, int) and not isinstance(number, float):
|
||||
raise TypeError("Not a Number")
|
||||
product *= number
|
||||
# Cannot calculate the even root for negative product.
|
||||
# Frequently they are restricted to being positive.
|
||||
if product < 0 and len(args) % 2 == 0:
|
||||
raise ArithmeticError("Cannot Compute Geometric Mean for these numbers.")
|
||||
mean = abs(product) ** (1 / len(args))
|
||||
# Since python calculates complex roots for negative products with odd roots.
|
||||
if product < 0:
|
||||
mean = -mean
|
||||
# Since it does floating point arithmetic, it gives 64**(1/3) as 3.99999996
|
||||
possible_mean = float(round(mean))
|
||||
# To check if the rounded number is actually the mean.
|
||||
if possible_mean ** len(args) == product:
|
||||
mean = possible_mean
|
||||
return mean
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod(name="compute_geometric_mean")
|
||||
print(compute_geometric_mean(-3, -27))
|
|
@ -1,13 +1,15 @@
|
|||
"""
|
||||
wiki: https://en.wikipedia.org/wiki/IPv4
|
||||
|
||||
Is IP v4 address valid?
|
||||
A valid IP address must be four octets in the form of A.B.C.D,
|
||||
where A,B,C and D are numbers from 0-254
|
||||
for example: 192.168.23.1, 172.254.254.254 are valid IP address
|
||||
192.168.255.0, 255.192.3.121 are invalid IP address
|
||||
where A, B, C and D are numbers from 0-255
|
||||
for example: 192.168.23.1, 172.255.255.255 are valid IP address
|
||||
192.168.256.0, 256.192.3.121 are invalid IP address
|
||||
"""
|
||||
|
||||
|
||||
def is_ip_v4_address_valid(ip_v4_address: str) -> bool:
|
||||
def is_ip_v4_address_valid(ip: str) -> bool:
|
||||
"""
|
||||
print "Valid IP address" If IP is valid.
|
||||
or
|
||||
|
@ -16,13 +18,13 @@ def is_ip_v4_address_valid(ip_v4_address: str) -> bool:
|
|||
>>> is_ip_v4_address_valid("192.168.0.23")
|
||||
True
|
||||
|
||||
>>> is_ip_v4_address_valid("192.255.15.8")
|
||||
>>> is_ip_v4_address_valid("192.256.15.8")
|
||||
False
|
||||
|
||||
>>> is_ip_v4_address_valid("172.100.0.8")
|
||||
True
|
||||
|
||||
>>> is_ip_v4_address_valid("254.255.0.255")
|
||||
>>> is_ip_v4_address_valid("255.256.0.256")
|
||||
False
|
||||
|
||||
>>> is_ip_v4_address_valid("1.2.33333333.4")
|
||||
|
@ -45,12 +47,29 @@ def is_ip_v4_address_valid(ip_v4_address: str) -> bool:
|
|||
|
||||
>>> is_ip_v4_address_valid("1.2.3.")
|
||||
False
|
||||
|
||||
>>> is_ip_v4_address_valid("1.2.3.05")
|
||||
False
|
||||
"""
|
||||
octets = [int(i) for i in ip_v4_address.split(".") if i.isdigit()]
|
||||
return len(octets) == 4 and all(0 <= int(octet) <= 254 for octet in octets)
|
||||
octets = ip.split(".")
|
||||
if len(octets) != 4:
|
||||
return False
|
||||
|
||||
for octet in octets:
|
||||
if not octet.isdigit():
|
||||
return False
|
||||
|
||||
number = int(octet)
|
||||
if len(str(number)) != len(octet):
|
||||
return False
|
||||
|
||||
if not 0 <= number <= 255:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ip = input().strip()
|
||||
valid_or_invalid = "valid" if is_ip_v4_address_valid(ip) else "invalid"
|
||||
print(f"{ip} is a {valid_or_invalid} IP v4 address.")
|
||||
print(f"{ip} is a {valid_or_invalid} IPv4 address.")
|
||||
|
|
|
@ -39,6 +39,21 @@ def modular_exponentiation(a, b):
|
|||
|
||||
|
||||
def fibonacci_with_matrix_exponentiation(n, f1, f2):
|
||||
"""
|
||||
Returns the nth number of the Fibonacci sequence that
|
||||
starts with f1 and f2
|
||||
Uses the matrix exponentiation
|
||||
>>> fibonacci_with_matrix_exponentiation(1, 5, 6)
|
||||
5
|
||||
>>> fibonacci_with_matrix_exponentiation(2, 10, 11)
|
||||
11
|
||||
>>> fibonacci_with_matrix_exponentiation(13, 0, 1)
|
||||
144
|
||||
>>> fibonacci_with_matrix_exponentiation(10, 5, 9)
|
||||
411
|
||||
>>> fibonacci_with_matrix_exponentiation(9, 2, 3)
|
||||
89
|
||||
"""
|
||||
# Trivial Cases
|
||||
if n == 1:
|
||||
return f1
|
||||
|
@ -50,21 +65,34 @@ def fibonacci_with_matrix_exponentiation(n, f1, f2):
|
|||
|
||||
|
||||
def simple_fibonacci(n, f1, f2):
|
||||
"""
|
||||
Returns the nth number of the Fibonacci sequence that
|
||||
starts with f1 and f2
|
||||
Uses the definition
|
||||
>>> simple_fibonacci(1, 5, 6)
|
||||
5
|
||||
>>> simple_fibonacci(2, 10, 11)
|
||||
11
|
||||
>>> simple_fibonacci(13, 0, 1)
|
||||
144
|
||||
>>> simple_fibonacci(10, 5, 9)
|
||||
411
|
||||
>>> simple_fibonacci(9, 2, 3)
|
||||
89
|
||||
"""
|
||||
# Trivial Cases
|
||||
if n == 1:
|
||||
return f1
|
||||
elif n == 2:
|
||||
return f2
|
||||
|
||||
fn_1 = f1
|
||||
fn_2 = f2
|
||||
n -= 2
|
||||
|
||||
while n > 0:
|
||||
fn_1, fn_2 = fn_1 + fn_2, fn_1
|
||||
f2, f1 = f1 + f2, f2
|
||||
n -= 1
|
||||
|
||||
return fn_1
|
||||
return f2
|
||||
|
||||
|
||||
def matrix_exponentiation_time():
|
||||
|
|
|
@ -43,4 +43,6 @@ if __name__ == "__main__":
|
|||
testmod()
|
||||
array = [randint(-1000, 1000) for i in range(100)]
|
||||
k = randint(0, 110)
|
||||
print(f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array,k)}")
|
||||
print(
|
||||
f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array, k)}"
|
||||
)
|
||||
|
|
|
@ -88,18 +88,18 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo
|
|||
AssertionError: precision should be positive integer your input : -1
|
||||
|
||||
"""
|
||||
assert callable(
|
||||
function
|
||||
), f"the function(object) passed should be callable your input : {function}"
|
||||
assert callable(function), (
|
||||
f"the function(object) passed should be callable your input : {function}"
|
||||
)
|
||||
assert isinstance(a, (float, int)), f"a should be float or integer your input : {a}"
|
||||
assert isinstance(function(a), (float, int)), (
|
||||
"the function should return integer or float return type of your function, "
|
||||
f"{type(a)}"
|
||||
)
|
||||
assert isinstance(b, (float, int)), f"b should be float or integer your input : {b}"
|
||||
assert (
|
||||
isinstance(precision, int) and precision > 0
|
||||
), f"precision should be positive integer your input : {precision}"
|
||||
assert isinstance(precision, int) and precision > 0, (
|
||||
f"precision should be positive integer your input : {precision}"
|
||||
)
|
||||
|
||||
# just applying the formula of simpson for approximate integration written in
|
||||
# mentioned article in first comment of this file and above this function
|
||||
|
|
|
@ -42,6 +42,11 @@ def intersection(function: Callable[[float], float], x0: float, x1: float) -> fl
|
|||
|
||||
|
||||
def f(x: float) -> float:
|
||||
"""
|
||||
function is f(x) = x^3 - 2x - 5
|
||||
>>> f(2)
|
||||
-1.0
|
||||
"""
|
||||
return math.pow(x, 3) - (2 * x) - 5
|
||||
|
||||
|
||||
|
|
|
@ -46,17 +46,27 @@ def perfect(number: int) -> bool:
|
|||
False
|
||||
>>> perfect(-1)
|
||||
False
|
||||
>>> perfect(33550336) # Large perfect number
|
||||
True
|
||||
>>> perfect(33550337) # Just above a large perfect number
|
||||
False
|
||||
>>> perfect(1) # Edge case: 1 is not a perfect number
|
||||
False
|
||||
>>> perfect("123") # String representation of a number
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: number must be an integer
|
||||
>>> perfect(12.34)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: number must an integer
|
||||
ValueError: number must be an integer
|
||||
>>> perfect("Hello")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: number must an integer
|
||||
ValueError: number must be an integer
|
||||
"""
|
||||
if not isinstance(number, int):
|
||||
raise ValueError("number must an integer")
|
||||
raise ValueError("number must be an integer")
|
||||
if number <= 0:
|
||||
return False
|
||||
return sum(i for i in range(1, number // 2 + 1) if number % i == 0) == number
|
||||
|
@ -70,8 +80,7 @@ if __name__ == "__main__":
|
|||
try:
|
||||
number = int(input("Enter a positive integer: ").strip())
|
||||
except ValueError:
|
||||
msg = "number must an integer"
|
||||
print(msg)
|
||||
msg = "number must be an integer"
|
||||
raise ValueError(msg)
|
||||
|
||||
print(f"{number} is {'' if perfect(number) else 'not '}a Perfect Number.")
|
||||
|
|
|
@ -38,6 +38,14 @@ def power(base: int, exponent: int) -> float:
|
|||
Traceback (most recent call last):
|
||||
...
|
||||
RecursionError: maximum recursion depth exceeded
|
||||
>>> power(0, 0)
|
||||
1
|
||||
>>> power(0, 1)
|
||||
0
|
||||
>>> power(5,6)
|
||||
15625
|
||||
>>> power(23, 12)
|
||||
21914624432020321
|
||||
"""
|
||||
return base * power(base, (exponent - 1)) if exponent else 1
|
||||
|
||||
|
|
|
@ -73,12 +73,12 @@ class Test(unittest.TestCase):
|
|||
def test_not_primes(self):
|
||||
with pytest.raises(ValueError):
|
||||
is_prime(-19)
|
||||
assert not is_prime(
|
||||
0
|
||||
), "Zero doesn't have any positive factors, primes must have exactly two."
|
||||
assert not is_prime(
|
||||
1
|
||||
), "One only has 1 positive factor, primes must have exactly two."
|
||||
assert not is_prime(0), (
|
||||
"Zero doesn't have any positive factors, primes must have exactly two."
|
||||
)
|
||||
assert not is_prime(1), (
|
||||
"One only has 1 positive factor, primes must have exactly two."
|
||||
)
|
||||
assert not is_prime(2 * 2)
|
||||
assert not is_prime(2 * 3)
|
||||
assert not is_prime(3 * 3)
|
||||
|
|
|
@ -2,7 +2,7 @@ import math
|
|||
from collections.abc import Generator
|
||||
|
||||
|
||||
def slow_primes(max_n: int) -> Generator[int, None, None]:
|
||||
def slow_primes(max_n: int) -> Generator[int]:
|
||||
"""
|
||||
Return a list of all primes numbers up to max.
|
||||
>>> list(slow_primes(0))
|
||||
|
@ -29,7 +29,7 @@ def slow_primes(max_n: int) -> Generator[int, None, None]:
|
|||
yield i
|
||||
|
||||
|
||||
def primes(max_n: int) -> Generator[int, None, None]:
|
||||
def primes(max_n: int) -> Generator[int]:
|
||||
"""
|
||||
Return a list of all primes numbers up to max.
|
||||
>>> list(primes(0))
|
||||
|
@ -58,7 +58,7 @@ def primes(max_n: int) -> Generator[int, None, None]:
|
|||
yield i
|
||||
|
||||
|
||||
def fast_primes(max_n: int) -> Generator[int, None, None]:
|
||||
def fast_primes(max_n: int) -> Generator[int]:
|
||||
"""
|
||||
Return a list of all primes numbers up to max.
|
||||
>>> list(fast_primes(0))
|
||||
|
|
|
@ -66,9 +66,9 @@ def is_prime(number: int) -> bool:
|
|||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(number, int) and (
|
||||
number >= 0
|
||||
), "'number' must been an int and positive"
|
||||
assert isinstance(number, int) and (number >= 0), (
|
||||
"'number' must been an int and positive"
|
||||
)
|
||||
|
||||
status = True
|
||||
|
||||
|
@ -254,9 +254,9 @@ def greatest_prime_factor(number):
|
|||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(number, int) and (
|
||||
number >= 0
|
||||
), "'number' must been an int and >= 0"
|
||||
assert isinstance(number, int) and (number >= 0), (
|
||||
"'number' must been an int and >= 0"
|
||||
)
|
||||
|
||||
ans = 0
|
||||
|
||||
|
@ -296,9 +296,9 @@ def smallest_prime_factor(number):
|
|||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(number, int) and (
|
||||
number >= 0
|
||||
), "'number' must been an int and >= 0"
|
||||
assert isinstance(number, int) and (number >= 0), (
|
||||
"'number' must been an int and >= 0"
|
||||
)
|
||||
|
||||
ans = 0
|
||||
|
||||
|
@ -399,9 +399,9 @@ def goldbach(number):
|
|||
"""
|
||||
|
||||
# precondition
|
||||
assert (
|
||||
isinstance(number, int) and (number > 2) and is_even(number)
|
||||
), "'number' must been an int, even and > 2"
|
||||
assert isinstance(number, int) and (number > 2) and is_even(number), (
|
||||
"'number' must been an int, even and > 2"
|
||||
)
|
||||
|
||||
ans = [] # this list will returned
|
||||
|
||||
|
@ -525,9 +525,9 @@ def kg_v(number1, number2):
|
|||
done.append(n)
|
||||
|
||||
# precondition
|
||||
assert isinstance(ans, int) and (
|
||||
ans >= 0
|
||||
), "'ans' must been from type int and positive"
|
||||
assert isinstance(ans, int) and (ans >= 0), (
|
||||
"'ans' must been from type int and positive"
|
||||
)
|
||||
|
||||
return ans
|
||||
|
||||
|
@ -574,9 +574,9 @@ def get_prime(n):
|
|||
ans += 1
|
||||
|
||||
# precondition
|
||||
assert isinstance(ans, int) and is_prime(
|
||||
ans
|
||||
), "'ans' must been a prime number and from type int"
|
||||
assert isinstance(ans, int) and is_prime(ans), (
|
||||
"'ans' must been a prime number and from type int"
|
||||
)
|
||||
|
||||
return ans
|
||||
|
||||
|
@ -705,9 +705,9 @@ def is_perfect_number(number):
|
|||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(number, int) and (
|
||||
number > 1
|
||||
), "'number' must been an int and >= 1"
|
||||
assert isinstance(number, int) and (number > 1), (
|
||||
"'number' must been an int and >= 1"
|
||||
)
|
||||
|
||||
divisors = get_divisors(number)
|
||||
|
||||
|
|
|
@ -43,9 +43,9 @@ def armstrong_number(n: int) -> bool:
|
|||
def pluperfect_number(n: int) -> bool:
|
||||
"""Return True if n is a pluperfect number or False if it is not
|
||||
|
||||
>>> all(armstrong_number(n) for n in PASSING)
|
||||
>>> all(pluperfect_number(n) for n in PASSING)
|
||||
True
|
||||
>>> any(armstrong_number(n) for n in FAILING)
|
||||
>>> any(pluperfect_number(n) for n in FAILING)
|
||||
False
|
||||
"""
|
||||
if not isinstance(n, int) or n < 1:
|
||||
|
@ -70,9 +70,9 @@ def pluperfect_number(n: int) -> bool:
|
|||
def narcissistic_number(n: int) -> bool:
|
||||
"""Return True if n is a narcissistic number or False if it is not.
|
||||
|
||||
>>> all(armstrong_number(n) for n in PASSING)
|
||||
>>> all(narcissistic_number(n) for n in PASSING)
|
||||
True
|
||||
>>> any(armstrong_number(n) for n in FAILING)
|
||||
>>> any(narcissistic_number(n) for n in FAILING)
|
||||
False
|
||||
"""
|
||||
if not isinstance(n, int) or n < 1:
|
||||
|
|
|
@ -21,6 +21,10 @@ def bell_numbers(max_set_length: int) -> list[int]:
|
|||
list: A list of Bell numbers for sets of lengths from 0 to max_set_length.
|
||||
|
||||
Examples:
|
||||
>>> bell_numbers(-2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: max_set_length must be non-negative
|
||||
>>> bell_numbers(0)
|
||||
[1]
|
||||
>>> bell_numbers(1)
|
||||
|
|
|
@ -13,6 +13,10 @@ def hamming(n_element: int) -> list:
|
|||
:param n_element: The number of elements on the list
|
||||
:return: The nth element of the list
|
||||
|
||||
>>> hamming(-5)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: n_element should be a positive number
|
||||
>>> hamming(5)
|
||||
[1, 2, 3, 4, 5]
|
||||
>>> hamming(10)
|
||||
|
@ -22,7 +26,7 @@ def hamming(n_element: int) -> list:
|
|||
"""
|
||||
n_element = int(n_element)
|
||||
if n_element < 1:
|
||||
my_error = ValueError("a should be a positive number")
|
||||
my_error = ValueError("n_element should be a positive number")
|
||||
raise my_error
|
||||
|
||||
hamming_list = [1]
|
||||
|
|
|
@ -11,6 +11,8 @@ def int_to_base(number: int, base: int) -> str:
|
|||
Where 'base' ranges from 2 to 36.
|
||||
|
||||
Examples:
|
||||
>>> int_to_base(0, 21)
|
||||
'0'
|
||||
>>> int_to_base(23, 2)
|
||||
'10111'
|
||||
>>> int_to_base(58, 5)
|
||||
|
@ -26,6 +28,10 @@ def int_to_base(number: int, base: int) -> str:
|
|||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 'base' must be between 2 and 36 inclusive
|
||||
>>> int_to_base(-99, 16)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: number must be a positive integer
|
||||
"""
|
||||
|
||||
if base < 2 or base > 36:
|
||||
|
@ -101,6 +107,8 @@ def harshad_numbers_in_base(limit: int, base: int) -> list[str]:
|
|||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 'base' must be between 2 and 36 inclusive
|
||||
>>> harshad_numbers_in_base(-12, 6)
|
||||
[]
|
||||
"""
|
||||
|
||||
if base < 2 or base > 36:
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
"""
|
||||
Numerical integration or quadrature for a smooth function f with known values at x_i
|
||||
|
||||
This method is the classical approach of suming 'Equally Spaced Abscissas'
|
||||
|
||||
method 1:
|
||||
"extended trapezoidal rule"
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def method_1(boundary, steps):
|
||||
# "extended trapezoidal rule"
|
||||
# int(f) = dx/2 * (f1 + 2f2 + ... + fn)
|
||||
def trapezoidal_rule(boundary, steps):
|
||||
"""
|
||||
Implements the extended trapezoidal rule for numerical integration.
|
||||
The function f(x) is provided below.
|
||||
|
||||
:param boundary: List containing the lower and upper bounds of integration [a, b]
|
||||
:param steps: The number of steps (intervals) used in the approximation
|
||||
:return: The numerical approximation of the integral
|
||||
|
||||
>>> abs(trapezoidal_rule([0, 1], 10) - 0.33333) < 0.01
|
||||
True
|
||||
>>> abs(trapezoidal_rule([0, 1], 100) - 0.33333) < 0.01
|
||||
True
|
||||
>>> abs(trapezoidal_rule([0, 2], 1000) - 2.66667) < 0.01
|
||||
True
|
||||
>>> abs(trapezoidal_rule([1, 2], 1000) - 2.33333) < 0.01
|
||||
True
|
||||
"""
|
||||
h = (boundary[1] - boundary[0]) / steps
|
||||
a = boundary[0]
|
||||
b = boundary[1]
|
||||
|
@ -19,32 +28,78 @@ def method_1(boundary, steps):
|
|||
y = 0.0
|
||||
y += (h / 2.0) * f(a)
|
||||
for i in x_i:
|
||||
# print(i)
|
||||
y += h * f(i)
|
||||
y += (h / 2.0) * f(b)
|
||||
return y
|
||||
|
||||
|
||||
def make_points(a, b, h):
|
||||
"""
|
||||
Generates points between a and b with step size h for trapezoidal integration.
|
||||
|
||||
:param a: The lower bound of integration
|
||||
:param b: The upper bound of integration
|
||||
:param h: The step size
|
||||
:yield: The next x-value in the range (a, b)
|
||||
|
||||
>>> list(make_points(0, 1, 0.1)) # doctest: +NORMALIZE_WHITESPACE
|
||||
[0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6, 0.7, 0.7999999999999999, \
|
||||
0.8999999999999999]
|
||||
>>> list(make_points(0, 10, 2.5))
|
||||
[2.5, 5.0, 7.5]
|
||||
>>> list(make_points(0, 10, 2))
|
||||
[2, 4, 6, 8]
|
||||
>>> list(make_points(1, 21, 5))
|
||||
[6, 11, 16]
|
||||
>>> list(make_points(1, 5, 2))
|
||||
[3]
|
||||
>>> list(make_points(1, 4, 3))
|
||||
[]
|
||||
"""
|
||||
x = a + h
|
||||
while x < (b - h):
|
||||
while x <= (b - h):
|
||||
yield x
|
||||
x = x + h
|
||||
x += h
|
||||
|
||||
|
||||
def f(x): # enter your function here
|
||||
y = (x - 0) * (x - 0)
|
||||
return y
|
||||
def f(x):
|
||||
"""
|
||||
This is the function to integrate, f(x) = (x - 0)^2 = x^2.
|
||||
|
||||
:param x: The input value
|
||||
:return: The value of f(x)
|
||||
|
||||
>>> f(0)
|
||||
0
|
||||
>>> f(1)
|
||||
1
|
||||
>>> f(0.5)
|
||||
0.25
|
||||
"""
|
||||
return x**2
|
||||
|
||||
|
||||
def main():
|
||||
a = 0.0 # Lower bound of integration
|
||||
b = 1.0 # Upper bound of integration
|
||||
steps = 10.0 # define number of steps or resolution
|
||||
boundary = [a, b] # define boundary of integration
|
||||
y = method_1(boundary, steps)
|
||||
"""
|
||||
Main function to test the trapezoidal rule.
|
||||
:a: Lower bound of integration
|
||||
:b: Upper bound of integration
|
||||
:steps: define number of steps or resolution
|
||||
:boundary: define boundary of integration
|
||||
|
||||
>>> main()
|
||||
y = 0.3349999999999999
|
||||
"""
|
||||
a = 0.0
|
||||
b = 1.0
|
||||
steps = 10.0
|
||||
boundary = [a, b]
|
||||
y = trapezoidal_rule(boundary, steps)
|
||||
print(f"y = {y}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
main()
|
||||
|
|
151
maths/volume.py
151
maths/volume.py
|
@ -1,17 +1,19 @@
|
|||
"""
|
||||
Find the volume of various shapes.
|
||||
|
||||
* https://en.wikipedia.org/wiki/Volume
|
||||
* https://en.wikipedia.org/wiki/Spherical_cap
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from math import pi, pow
|
||||
from math import pi, pow # noqa: A004
|
||||
|
||||
|
||||
def vol_cube(side_length: float) -> float:
|
||||
"""
|
||||
Calculate the Volume of a Cube.
|
||||
|
||||
>>> vol_cube(1)
|
||||
1.0
|
||||
>>> vol_cube(3)
|
||||
|
@ -33,6 +35,7 @@ def vol_cube(side_length: float) -> float:
|
|||
def vol_spherical_cap(height: float, radius: float) -> float:
|
||||
"""
|
||||
Calculate the volume of the spherical cap.
|
||||
|
||||
>>> vol_spherical_cap(1, 2)
|
||||
5.235987755982988
|
||||
>>> vol_spherical_cap(1.6, 2.6)
|
||||
|
@ -57,20 +60,29 @@ def vol_spherical_cap(height: float, radius: float) -> float:
|
|||
def vol_spheres_intersect(
|
||||
radius_1: float, radius_2: float, centers_distance: float
|
||||
) -> float:
|
||||
"""
|
||||
r"""
|
||||
Calculate the volume of the intersection of two spheres.
|
||||
|
||||
The intersection is composed by two spherical caps and therefore its volume is the
|
||||
sum of the volumes of the spherical caps. First, it calculates the heights (h1, h2)
|
||||
of the spherical caps, then the two volumes and it returns the sum.
|
||||
sum of the volumes of the spherical caps.
|
||||
First, it calculates the heights :math:`(h_1, h_2)` of the spherical caps,
|
||||
then the two volumes and it returns the sum.
|
||||
The height formulas are
|
||||
h1 = (radius_1 - radius_2 + centers_distance)
|
||||
* (radius_1 + radius_2 - centers_distance)
|
||||
/ (2 * centers_distance)
|
||||
h2 = (radius_2 - radius_1 + centers_distance)
|
||||
* (radius_2 + radius_1 - centers_distance)
|
||||
/ (2 * centers_distance)
|
||||
if centers_distance is 0 then it returns the volume of the smallers sphere
|
||||
:return vol_spherical_cap(h1, radius_2) + vol_spherical_cap(h2, radius_1)
|
||||
|
||||
.. math::
|
||||
h_1 = \frac{(radius_1 - radius_2 + centers\_distance)
|
||||
\cdot (radius_1 + radius_2 - centers\_distance)}
|
||||
{2 \cdot centers\_distance}
|
||||
|
||||
h_2 = \frac{(radius_2 - radius_1 + centers\_distance)
|
||||
\cdot (radius_2 + radius_1 - centers\_distance)}
|
||||
{2 \cdot centers\_distance}
|
||||
|
||||
if `centers_distance` is 0 then it returns the volume of the smallers sphere
|
||||
|
||||
:return: ``vol_spherical_cap`` (:math:`h_1`, :math:`radius_2`)
|
||||
+ ``vol_spherical_cap`` (:math:`h_2`, :math:`radius_1`)
|
||||
|
||||
>>> vol_spheres_intersect(2, 2, 1)
|
||||
21.205750411731103
|
||||
>>> vol_spheres_intersect(2.6, 2.6, 1.6)
|
||||
|
@ -112,14 +124,18 @@ def vol_spheres_intersect(
|
|||
def vol_spheres_union(
|
||||
radius_1: float, radius_2: float, centers_distance: float
|
||||
) -> float:
|
||||
"""
|
||||
r"""
|
||||
Calculate the volume of the union of two spheres that possibly intersect.
|
||||
It is the sum of sphere A and sphere B minus their intersection.
|
||||
First, it calculates the volumes (v1, v2) of the spheres,
|
||||
then the volume of the intersection (i) and it returns the sum v1+v2-i.
|
||||
If centers_distance is 0 then it returns the volume of the larger sphere
|
||||
:return vol_sphere(radius_1) + vol_sphere(radius_2)
|
||||
- vol_spheres_intersect(radius_1, radius_2, centers_distance)
|
||||
|
||||
It is the sum of sphere :math:`A` and sphere :math:`B` minus their intersection.
|
||||
First, it calculates the volumes :math:`(v_1, v_2)` of the spheres,
|
||||
then the volume of the intersection :math:`i` and
|
||||
it returns the sum :math:`v_1 + v_2 - i`.
|
||||
If `centers_distance` is 0 then it returns the volume of the larger sphere
|
||||
|
||||
:return: ``vol_sphere`` (:math:`radius_1`) + ``vol_sphere`` (:math:`radius_2`)
|
||||
- ``vol_spheres_intersect``
|
||||
(:math:`radius_1`, :math:`radius_2`, :math:`centers\_distance`)
|
||||
|
||||
>>> vol_spheres_union(2, 2, 1)
|
||||
45.814892864851146
|
||||
|
@ -157,7 +173,9 @@ def vol_spheres_union(
|
|||
def vol_cuboid(width: float, height: float, length: float) -> float:
|
||||
"""
|
||||
Calculate the Volume of a Cuboid.
|
||||
:return multiple of width, length and height
|
||||
|
||||
:return: multiple of `width`, `length` and `height`
|
||||
|
||||
>>> vol_cuboid(1, 1, 1)
|
||||
1.0
|
||||
>>> vol_cuboid(1, 2, 3)
|
||||
|
@ -185,10 +203,12 @@ def vol_cuboid(width: float, height: float, length: float) -> float:
|
|||
|
||||
|
||||
def vol_cone(area_of_base: float, height: float) -> float:
|
||||
"""
|
||||
Calculate the Volume of a Cone.
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Cone
|
||||
:return (1/3) * area_of_base * height
|
||||
r"""
|
||||
| Calculate the Volume of a Cone.
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Cone
|
||||
|
||||
:return: :math:`\frac{1}{3} \cdot area\_of\_base \cdot height`
|
||||
|
||||
>>> vol_cone(10, 3)
|
||||
10.0
|
||||
>>> vol_cone(1, 1)
|
||||
|
@ -212,10 +232,12 @@ def vol_cone(area_of_base: float, height: float) -> float:
|
|||
|
||||
|
||||
def vol_right_circ_cone(radius: float, height: float) -> float:
|
||||
"""
|
||||
Calculate the Volume of a Right Circular Cone.
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Cone
|
||||
:return (1/3) * pi * radius^2 * height
|
||||
r"""
|
||||
| Calculate the Volume of a Right Circular Cone.
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Cone
|
||||
|
||||
:return: :math:`\frac{1}{3} \cdot \pi \cdot radius^2 \cdot height`
|
||||
|
||||
>>> vol_right_circ_cone(2, 3)
|
||||
12.566370614359172
|
||||
>>> vol_right_circ_cone(0, 0)
|
||||
|
@ -237,10 +259,12 @@ def vol_right_circ_cone(radius: float, height: float) -> float:
|
|||
|
||||
|
||||
def vol_prism(area_of_base: float, height: float) -> float:
|
||||
"""
|
||||
Calculate the Volume of a Prism.
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Prism_(geometry)
|
||||
:return V = Bh
|
||||
r"""
|
||||
| Calculate the Volume of a Prism.
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Prism_(geometry)
|
||||
|
||||
:return: :math:`V = B \cdot h`
|
||||
|
||||
>>> vol_prism(10, 2)
|
||||
20.0
|
||||
>>> vol_prism(11, 1)
|
||||
|
@ -264,10 +288,12 @@ def vol_prism(area_of_base: float, height: float) -> float:
|
|||
|
||||
|
||||
def vol_pyramid(area_of_base: float, height: float) -> float:
|
||||
"""
|
||||
Calculate the Volume of a Pyramid.
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Pyramid_(geometry)
|
||||
:return (1/3) * Bh
|
||||
r"""
|
||||
| Calculate the Volume of a Pyramid.
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Pyramid_(geometry)
|
||||
|
||||
:return: :math:`\frac{1}{3} \cdot B \cdot h`
|
||||
|
||||
>>> vol_pyramid(10, 3)
|
||||
10.0
|
||||
>>> vol_pyramid(1.5, 3)
|
||||
|
@ -291,10 +317,12 @@ def vol_pyramid(area_of_base: float, height: float) -> float:
|
|||
|
||||
|
||||
def vol_sphere(radius: float) -> float:
|
||||
"""
|
||||
Calculate the Volume of a Sphere.
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Sphere
|
||||
:return (4/3) * pi * r^3
|
||||
r"""
|
||||
| Calculate the Volume of a Sphere.
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Sphere
|
||||
|
||||
:return: :math:`\frac{4}{3} \cdot \pi \cdot r^3`
|
||||
|
||||
>>> vol_sphere(5)
|
||||
523.5987755982989
|
||||
>>> vol_sphere(1)
|
||||
|
@ -315,10 +343,13 @@ def vol_sphere(radius: float) -> float:
|
|||
|
||||
|
||||
def vol_hemisphere(radius: float) -> float:
|
||||
"""Calculate the volume of a hemisphere
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Hemisphere
|
||||
Other references: https://www.cuemath.com/geometry/hemisphere
|
||||
:return 2/3 * pi * radius^3
|
||||
r"""
|
||||
| Calculate the volume of a hemisphere
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Hemisphere
|
||||
| Other references: https://www.cuemath.com/geometry/hemisphere
|
||||
|
||||
:return: :math:`\frac{2}{3} \cdot \pi \cdot radius^3`
|
||||
|
||||
>>> vol_hemisphere(1)
|
||||
2.0943951023931953
|
||||
>>> vol_hemisphere(7)
|
||||
|
@ -339,9 +370,12 @@ def vol_hemisphere(radius: float) -> float:
|
|||
|
||||
|
||||
def vol_circular_cylinder(radius: float, height: float) -> float:
|
||||
"""Calculate the Volume of a Circular Cylinder.
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder
|
||||
:return pi * radius^2 * height
|
||||
r"""
|
||||
| Calculate the Volume of a Circular Cylinder.
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder
|
||||
|
||||
:return: :math:`\pi \cdot radius^2 \cdot height`
|
||||
|
||||
>>> vol_circular_cylinder(1, 1)
|
||||
3.141592653589793
|
||||
>>> vol_circular_cylinder(4, 3)
|
||||
|
@ -368,7 +402,9 @@ def vol_circular_cylinder(radius: float, height: float) -> float:
|
|||
def vol_hollow_circular_cylinder(
|
||||
inner_radius: float, outer_radius: float, height: float
|
||||
) -> float:
|
||||
"""Calculate the Volume of a Hollow Circular Cylinder.
|
||||
"""
|
||||
Calculate the Volume of a Hollow Circular Cylinder.
|
||||
|
||||
>>> vol_hollow_circular_cylinder(1, 2, 3)
|
||||
28.274333882308138
|
||||
>>> vol_hollow_circular_cylinder(1.6, 2.6, 3.6)
|
||||
|
@ -405,8 +441,9 @@ def vol_hollow_circular_cylinder(
|
|||
|
||||
|
||||
def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> float:
|
||||
"""Calculate the Volume of a Conical Frustum.
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Frustum
|
||||
"""
|
||||
| Calculate the Volume of a Conical Frustum.
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Frustum
|
||||
|
||||
>>> vol_conical_frustum(45, 7, 28)
|
||||
48490.482608158454
|
||||
|
@ -443,9 +480,12 @@ def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> floa
|
|||
|
||||
|
||||
def vol_torus(torus_radius: float, tube_radius: float) -> float:
|
||||
"""Calculate the Volume of a Torus.
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Torus
|
||||
:return 2pi^2 * torus_radius * tube_radius^2
|
||||
r"""
|
||||
| Calculate the Volume of a Torus.
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Torus
|
||||
|
||||
:return: :math:`2 \pi^2 \cdot torus\_radius \cdot tube\_radius^2`
|
||||
|
||||
>>> vol_torus(1, 1)
|
||||
19.739208802178716
|
||||
>>> vol_torus(4, 3)
|
||||
|
@ -471,8 +511,9 @@ def vol_torus(torus_radius: float, tube_radius: float) -> float:
|
|||
|
||||
|
||||
def vol_icosahedron(tri_side: float) -> float:
|
||||
"""Calculate the Volume of an Icosahedron.
|
||||
Wikipedia reference: https://en.wikipedia.org/wiki/Regular_icosahedron
|
||||
"""
|
||||
| Calculate the Volume of an Icosahedron.
|
||||
| Wikipedia reference: https://en.wikipedia.org/wiki/Regular_icosahedron
|
||||
|
||||
>>> from math import isclose
|
||||
>>> isclose(vol_icosahedron(2.5), 34.088984228514256)
|
||||
|
|
|
@ -4,13 +4,14 @@ import datetime
|
|||
|
||||
def zeller(date_input: str) -> str:
|
||||
"""
|
||||
Zellers Congruence Algorithm
|
||||
Find the day of the week for nearly any Gregorian or Julian calendar date
|
||||
| Zellers Congruence Algorithm
|
||||
| Find the day of the week for nearly any Gregorian or Julian calendar date
|
||||
|
||||
>>> zeller('01-31-2010')
|
||||
'Your date 01-31-2010, is a Sunday!'
|
||||
|
||||
Validate out of range month
|
||||
Validate out of range month:
|
||||
|
||||
>>> zeller('13-31-2010')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -21,6 +22,7 @@ def zeller(date_input: str) -> str:
|
|||
ValueError: invalid literal for int() with base 10: '.2'
|
||||
|
||||
Validate out of range date:
|
||||
|
||||
>>> zeller('01-33-2010')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -31,30 +33,35 @@ def zeller(date_input: str) -> str:
|
|||
ValueError: invalid literal for int() with base 10: '.4'
|
||||
|
||||
Validate second separator:
|
||||
|
||||
>>> zeller('01-31*2010')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Date separator must be '-' or '/'
|
||||
|
||||
Validate first separator:
|
||||
|
||||
>>> zeller('01^31-2010')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Date separator must be '-' or '/'
|
||||
|
||||
Validate out of range year:
|
||||
|
||||
>>> zeller('01-31-8999')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Year out of range. There has to be some sort of limit...right?
|
||||
|
||||
Test null input:
|
||||
|
||||
>>> zeller()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: zeller() missing 1 required positional argument: 'date_input'
|
||||
|
||||
Test length of date_input:
|
||||
Test length of `date_input`:
|
||||
|
||||
>>> zeller('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
|
284
matrix/matrix_based_game.py
Normal file
284
matrix/matrix_based_game.py
Normal file
|
@ -0,0 +1,284 @@
|
|||
"""
|
||||
Matrix-Based Game Script
|
||||
=========================
|
||||
This script implements a matrix-based game where players interact with a grid of
|
||||
elements. The primary goals are to:
|
||||
- Identify connected elements of the same type from a selected position.
|
||||
- Remove those elements, adjust the matrix by simulating gravity, and reorganize empty
|
||||
columns.
|
||||
- Calculate and display the score based on the number of elements removed in each move.
|
||||
|
||||
Functions:
|
||||
-----------
|
||||
1. `find_repeat`: Finds all connected elements of the same type.
|
||||
2. `increment_score`: Calculates the score for a given move.
|
||||
3. `move_x`: Simulates gravity in a column.
|
||||
4. `move_y`: Reorganizes the matrix by shifting columns leftward when a column becomes
|
||||
empty.
|
||||
5. `play`: Executes a single move, updating the matrix and returning the score.
|
||||
|
||||
Input Format:
|
||||
--------------
|
||||
1. Matrix size (`lines`): Integer specifying the size of the matrix (N x N).
|
||||
2. Matrix content (`matrix`): Rows of the matrix, each consisting of characters.
|
||||
3. Number of moves (`movs`): Integer indicating the number of moves.
|
||||
4. List of moves (`movements`): A comma-separated string of coordinates for each move.
|
||||
|
||||
(0,0) position starts from first left column to last right, and below row to up row
|
||||
|
||||
|
||||
Example Input:
|
||||
---------------
|
||||
4
|
||||
RRBG
|
||||
RBBG
|
||||
YYGG
|
||||
XYGG
|
||||
2
|
||||
0 1,1 1
|
||||
|
||||
Example (0,0) = X
|
||||
|
||||
Output:
|
||||
--------
|
||||
The script outputs the total score after processing all moves.
|
||||
|
||||
Usage:
|
||||
-------
|
||||
Run the script and provide the required inputs as prompted.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def validate_matrix_size(size: int) -> None:
|
||||
"""
|
||||
>>> validate_matrix_size(-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Matrix size must be a positive integer.
|
||||
"""
|
||||
if not isinstance(size, int) or size <= 0:
|
||||
raise ValueError("Matrix size must be a positive integer.")
|
||||
|
||||
|
||||
def validate_matrix_content(matrix: list[str], size: int) -> None:
|
||||
"""
|
||||
Validates that the number of elements in the matrix matches the given size.
|
||||
|
||||
>>> validate_matrix_content(['aaaa', 'aaaa', 'aaaa', 'aaaa'], 3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: The matrix dont match with size.
|
||||
>>> validate_matrix_content(['aa%', 'aaa', 'aaa'], 3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Matrix rows can only contain letters and numbers.
|
||||
>>> validate_matrix_content(['aaa', 'aaa', 'aaaa'], 3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Each row in the matrix must have exactly 3 characters.
|
||||
"""
|
||||
print(matrix)
|
||||
if len(matrix) != size:
|
||||
raise ValueError("The matrix dont match with size.")
|
||||
for row in matrix:
|
||||
if len(row) != size:
|
||||
msg = f"Each row in the matrix must have exactly {size} characters."
|
||||
raise ValueError(msg)
|
||||
if not all(char.isalnum() for char in row):
|
||||
raise ValueError("Matrix rows can only contain letters and numbers.")
|
||||
|
||||
|
||||
def validate_moves(moves: list[tuple[int, int]], size: int) -> None:
|
||||
"""
|
||||
>>> validate_moves([(1, 2), (-1, 0)], 3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Move is out of bounds for a matrix.
|
||||
"""
|
||||
for move in moves:
|
||||
x, y = move
|
||||
if not (0 <= x < size and 0 <= y < size):
|
||||
raise ValueError("Move is out of bounds for a matrix.")
|
||||
|
||||
|
||||
def parse_moves(input_str: str) -> list[tuple[int, int]]:
|
||||
"""
|
||||
>>> parse_moves("0 1, 1 1")
|
||||
[(0, 1), (1, 1)]
|
||||
>>> parse_moves("0 1, 1 1, 2")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Each move must have exactly two numbers.
|
||||
>>> parse_moves("0 1, 1 1, 2 4 5 6")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Each move must have exactly two numbers.
|
||||
"""
|
||||
moves = []
|
||||
for pair in input_str.split(","):
|
||||
parts = pair.strip().split()
|
||||
if len(parts) != 2:
|
||||
raise ValueError("Each move must have exactly two numbers.")
|
||||
x, y = map(int, parts)
|
||||
moves.append((x, y))
|
||||
return moves
|
||||
|
||||
|
||||
def find_repeat(
|
||||
matrix_g: list[list[str]], row: int, column: int, size: int
|
||||
) -> set[tuple[int, int]]:
|
||||
"""
|
||||
Finds all connected elements of the same type from a given position.
|
||||
|
||||
>>> find_repeat([['A', 'B', 'A'], ['A', 'B', 'A'], ['A', 'A', 'A']], 0, 0, 3)
|
||||
{(1, 2), (2, 1), (0, 0), (2, 0), (0, 2), (2, 2), (1, 0)}
|
||||
>>> find_repeat([['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-']], 1, 1, 3)
|
||||
set()
|
||||
"""
|
||||
|
||||
column = size - 1 - column
|
||||
visited = set()
|
||||
repeated = set()
|
||||
|
||||
if (color := matrix_g[column][row]) != "-":
|
||||
|
||||
def dfs(row_n: int, column_n: int) -> None:
|
||||
if row_n < 0 or row_n >= size or column_n < 0 or column_n >= size:
|
||||
return
|
||||
if (row_n, column_n) in visited:
|
||||
return
|
||||
visited.add((row_n, column_n))
|
||||
if matrix_g[row_n][column_n] == color:
|
||||
repeated.add((row_n, column_n))
|
||||
dfs(row_n - 1, column_n)
|
||||
dfs(row_n + 1, column_n)
|
||||
dfs(row_n, column_n - 1)
|
||||
dfs(row_n, column_n + 1)
|
||||
|
||||
dfs(column, row)
|
||||
|
||||
return repeated
|
||||
|
||||
|
||||
def increment_score(count: int) -> int:
|
||||
"""
|
||||
Calculates the score for a move based on the number of elements removed.
|
||||
|
||||
>>> increment_score(3)
|
||||
6
|
||||
>>> increment_score(0)
|
||||
0
|
||||
"""
|
||||
return int(count * (count + 1) / 2)
|
||||
|
||||
|
||||
def move_x(matrix_g: list[list[str]], column: int, size: int) -> list[list[str]]:
|
||||
"""
|
||||
Simulates gravity in a specific column.
|
||||
|
||||
>>> move_x([['-', 'A'], ['-', '-'], ['-', 'C']], 1, 2)
|
||||
[['-', '-'], ['-', 'A'], ['-', 'C']]
|
||||
"""
|
||||
|
||||
new_list = []
|
||||
|
||||
for row in range(size):
|
||||
if matrix_g[row][column] != "-":
|
||||
new_list.append(matrix_g[row][column])
|
||||
else:
|
||||
new_list.insert(0, matrix_g[row][column])
|
||||
for row in range(size):
|
||||
matrix_g[row][column] = new_list[row]
|
||||
return matrix_g
|
||||
|
||||
|
||||
def move_y(matrix_g: list[list[str]], size: int) -> list[list[str]]:
|
||||
"""
|
||||
Shifts all columns leftward when an entire column becomes empty.
|
||||
|
||||
>>> move_y([['-', 'A'], ['-', '-'], ['-', 'C']], 2)
|
||||
[['A', '-'], ['-', '-'], ['-', 'C']]
|
||||
"""
|
||||
|
||||
empty_columns = []
|
||||
|
||||
for column in range(size - 1, -1, -1):
|
||||
if all(matrix_g[row][column] == "-" for row in range(size)):
|
||||
empty_columns.append(column)
|
||||
|
||||
for column in empty_columns:
|
||||
for col in range(column + 1, size):
|
||||
for row in range(size):
|
||||
matrix_g[row][col - 1] = matrix_g[row][col]
|
||||
for row in range(size):
|
||||
matrix_g[row][-1] = "-"
|
||||
|
||||
return matrix_g
|
||||
|
||||
|
||||
def play(
|
||||
matrix_g: list[list[str]], pos_x: int, pos_y: int, size: int
|
||||
) -> tuple[list[list[str]], int]:
|
||||
"""
|
||||
Processes a single move, updating the matrix and calculating the score.
|
||||
|
||||
>>> play([['R', 'G'], ['R', 'G']], 0, 0, 2)
|
||||
([['G', '-'], ['G', '-']], 3)
|
||||
"""
|
||||
|
||||
same_colors = find_repeat(matrix_g, pos_x, pos_y, size)
|
||||
|
||||
if len(same_colors) != 0:
|
||||
for pos in same_colors:
|
||||
matrix_g[pos[0]][pos[1]] = "-"
|
||||
for column in range(size):
|
||||
matrix_g = move_x(matrix_g, column, size)
|
||||
|
||||
matrix_g = move_y(matrix_g, size)
|
||||
|
||||
return (matrix_g, increment_score(len(same_colors)))
|
||||
|
||||
|
||||
def process_game(size: int, matrix: list[str], moves: list[tuple[int, int]]) -> int:
|
||||
"""Processes the game logic for the given matrix and moves.
|
||||
|
||||
Args:
|
||||
size (int): Size of the game board.
|
||||
matrix (List[str]): Initial game matrix.
|
||||
moves (List[Tuple[int, int]]): List of moves as (x, y) coordinates.
|
||||
|
||||
Returns:
|
||||
int: The total score obtained.
|
||||
>>> process_game(3, ['aaa', 'bbb', 'ccc'], [(0, 0)])
|
||||
6
|
||||
"""
|
||||
|
||||
game_matrix = [list(row) for row in matrix]
|
||||
total_score = 0
|
||||
|
||||
for move in moves:
|
||||
pos_x, pos_y = move
|
||||
game_matrix, score = play(game_matrix, pos_x, pos_y, size)
|
||||
total_score += score
|
||||
|
||||
return total_score
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod(verbose=True)
|
||||
try:
|
||||
size = int(input("Enter the size of the matrix: "))
|
||||
validate_matrix_size(size)
|
||||
print(f"Enter the {size} rows of the matrix:")
|
||||
matrix = [input(f"Row {i + 1}: ") for i in range(size)]
|
||||
validate_matrix_content(matrix, size)
|
||||
moves_input = input("Enter the moves (e.g., '0 0, 1 1'): ")
|
||||
moves = parse_moves(moves_input)
|
||||
validate_moves(moves, size)
|
||||
score = process_game(size, matrix, moves)
|
||||
print(f"Total score: {score}")
|
||||
except ValueError as e:
|
||||
print(f"{e}")
|
|
@ -61,9 +61,8 @@ def _extract_images(f):
|
|||
with gzip.GzipFile(fileobj=f) as bytestream:
|
||||
magic = _read32(bytestream)
|
||||
if magic != 2051:
|
||||
raise ValueError(
|
||||
"Invalid magic number %d in MNIST image file: %s" % (magic, f.name)
|
||||
)
|
||||
msg = f"Invalid magic number {magic} in MNIST image file: {f.name}"
|
||||
raise ValueError(msg)
|
||||
num_images = _read32(bytestream)
|
||||
rows = _read32(bytestream)
|
||||
cols = _read32(bytestream)
|
||||
|
@ -102,9 +101,8 @@ def _extract_labels(f, one_hot=False, num_classes=10):
|
|||
with gzip.GzipFile(fileobj=f) as bytestream:
|
||||
magic = _read32(bytestream)
|
||||
if magic != 2049:
|
||||
raise ValueError(
|
||||
"Invalid magic number %d in MNIST label file: %s" % (magic, f.name)
|
||||
)
|
||||
msg = f"Invalid magic number {magic} in MNIST label file: {f.name}"
|
||||
raise ValueError(msg)
|
||||
num_items = _read32(bytestream)
|
||||
buf = bytestream.read(num_items)
|
||||
labels = np.frombuffer(buf, dtype=np.uint8)
|
||||
|
@ -162,9 +160,9 @@ class _DataSet:
|
|||
self._num_examples = 10000
|
||||
self.one_hot = one_hot
|
||||
else:
|
||||
assert (
|
||||
images.shape[0] == labels.shape[0]
|
||||
), f"images.shape: {images.shape} labels.shape: {labels.shape}"
|
||||
assert images.shape[0] == labels.shape[0], (
|
||||
f"images.shape: {images.shape} labels.shape: {labels.shape}"
|
||||
)
|
||||
self._num_examples = images.shape[0]
|
||||
|
||||
# Convert shape from [num examples, rows, columns, depth]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user