Merge branch 'TheAlgorithms:master' into game_of_life_tests

This commit is contained in:
Jordan Sinclair 2025-01-14 21:14:35 -06:00 committed by GitHub
commit f03d77ade5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
130 changed files with 3740 additions and 1047 deletions

View File

@ -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

View File

@ -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 }}

View File

@ -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 .

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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([

View File

@ -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")

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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
"""

View File

@ -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))

View File

@ -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]

View File

@ -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()

View File

@ -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

View File

@ -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'
"""

View File

@ -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:]

View File

@ -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/>

View File

@ -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]}"

View 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()

View File

@ -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]

View File

@ -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",

View 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()

View File

@ -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()

View File

@ -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):

View File

@ -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.

View File

@ -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():

View File

@ -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

View File

@ -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])

View File

@ -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():

View File

@ -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

View File

@ -33,7 +33,7 @@ class Deque:
the number of nodes
"""
__slots__ = ("_front", "_back", "_len")
__slots__ = ("_back", "_front", "_len")
@dataclass
class _Node:

View File

@ -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
View File

View 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"])

View File

@ -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

View File

@ -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.
"""

View File

@ -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)

View File

@ -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

View File

@ -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).

View File

@ -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","")

View File

@ -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

View File

@ -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()

View File

@ -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__":

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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}"

View File

@ -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:

View File

@ -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):
...

View File

@ -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")

View File

@ -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__":

View File

@ -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(

View File

@ -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)

View File

@ -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:

View 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))

View 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()

View File

@ -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
)

View File

@ -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):

View File

@ -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]})

View 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()

View File

@ -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

View File

@ -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]])

View File

@ -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

View File

@ -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 ],

View File

@ -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):

View File

@ -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, :] -= (

View File

@ -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: ...

View File

@ -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]]

View File

@ -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]])

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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()

View File

@ -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))

View File

@ -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
View 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))

View File

@ -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.")

View File

@ -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():

View File

@ -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)}"
)

View File

@ -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

View File

@ -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

View File

@ -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.")

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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]

View File

@ -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:

View File

@ -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()

View File

@ -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)

View File

@ -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
View 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}")

View File

@ -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