mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-02-25 10:28:39 +00:00
Compare commits
25 Commits
b03c5a3ba0
...
3040f99707
Author | SHA1 | Date | |
---|---|---|---|
|
3040f99707 | ||
|
8be37da62a | ||
|
33f7f8e78d | ||
|
0f3e4bb3c4 | ||
|
f6b12420ce | ||
|
fd7cc4cf8e | ||
|
beb43517c3 | ||
|
5c276a8377 | ||
|
bfed2fb788 | ||
|
f66568e981 | ||
|
efaf526737 | ||
|
cecf1fdd52 | ||
|
490e645ed3 | ||
|
7618a92fee | ||
|
7021afda04 | ||
|
fb1b939a89 | ||
|
2ab3bf2689 | ||
|
ac68dc1128 | ||
|
4b7ecb6a81 | ||
|
c290dd6a43 | ||
|
02d89bde67 | ||
|
f24ab2c60d | ||
|
9d86d4edaa | ||
|
4f2a346c27 | ||
|
7b0ac718b0 |
@ -16,7 +16,7 @@ repos:
|
||||
- id: auto-walrus
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.0.282
|
||||
rev: v0.0.284
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
@ -33,7 +33,7 @@ repos:
|
||||
- tomli
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "0.13.0"
|
||||
rev: "0.13.1"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
@ -51,7 +51,7 @@ repos:
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.4.1
|
||||
rev: v1.5.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
args:
|
||||
|
@ -62,7 +62,7 @@
|
||||
## Boolean Algebra
|
||||
* [And Gate](boolean_algebra/and_gate.py)
|
||||
* [Nand Gate](boolean_algebra/nand_gate.py)
|
||||
* [Norgate](boolean_algebra/norgate.py)
|
||||
* [Nor Gate](boolean_algebra/nor_gate.py)
|
||||
* [Not Gate](boolean_algebra/not_gate.py)
|
||||
* [Or Gate](boolean_algebra/or_gate.py)
|
||||
* [Quine Mc Cluskey](boolean_algebra/quine_mc_cluskey.py)
|
||||
@ -74,6 +74,7 @@
|
||||
* [Game Of Life](cellular_automata/game_of_life.py)
|
||||
* [Nagel Schrekenberg](cellular_automata/nagel_schrekenberg.py)
|
||||
* [One Dimensional](cellular_automata/one_dimensional.py)
|
||||
* [Wa Tor](cellular_automata/wa_tor.py)
|
||||
|
||||
## Ciphers
|
||||
* [A1Z26](ciphers/a1z26.py)
|
||||
@ -335,9 +336,11 @@
|
||||
* [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py)
|
||||
* [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py)
|
||||
* [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py)
|
||||
* [Regex Match](dynamic_programming/regex_match.py)
|
||||
* [Rod Cutting](dynamic_programming/rod_cutting.py)
|
||||
* [Subset Generation](dynamic_programming/subset_generation.py)
|
||||
* [Sum Of Subset](dynamic_programming/sum_of_subset.py)
|
||||
* [Tribonacci](dynamic_programming/tribonacci.py)
|
||||
* [Viterbi](dynamic_programming/viterbi.py)
|
||||
* [Word Break](dynamic_programming/word_break.py)
|
||||
|
||||
@ -552,6 +555,7 @@
|
||||
* [Chudnovsky Algorithm](maths/chudnovsky_algorithm.py)
|
||||
* [Collatz Sequence](maths/collatz_sequence.py)
|
||||
* [Combinations](maths/combinations.py)
|
||||
* [Continued Fraction](maths/continued_fraction.py)
|
||||
* [Decimal Isolate](maths/decimal_isolate.py)
|
||||
* [Decimal To Fraction](maths/decimal_to_fraction.py)
|
||||
* [Dodecahedron](maths/dodecahedron.py)
|
||||
@ -570,9 +574,7 @@
|
||||
* [Fermat Little Theorem](maths/fermat_little_theorem.py)
|
||||
* [Fibonacci](maths/fibonacci.py)
|
||||
* [Find Max](maths/find_max.py)
|
||||
* [Find Max Recursion](maths/find_max_recursion.py)
|
||||
* [Find Min](maths/find_min.py)
|
||||
* [Find Min Recursion](maths/find_min_recursion.py)
|
||||
* [Floor](maths/floor.py)
|
||||
* [Gamma](maths/gamma.py)
|
||||
* [Gamma Recursive](maths/gamma_recursive.py)
|
||||
@ -1169,6 +1171,7 @@
|
||||
* [Is Pangram](strings/is_pangram.py)
|
||||
* [Is Spain National Id](strings/is_spain_national_id.py)
|
||||
* [Is Srilankan Phone Number](strings/is_srilankan_phone_number.py)
|
||||
* [Is Valid Email Address](strings/is_valid_email_address.py)
|
||||
* [Jaro Winkler](strings/jaro_winkler.py)
|
||||
* [Join](strings/join.py)
|
||||
* [Knuth Morris Pratt](strings/knuth_morris_pratt.py)
|
||||
|
@ -74,10 +74,7 @@ def is_for_table(string1: str, string2: str, count: int) -> bool:
|
||||
"""
|
||||
list1 = list(string1)
|
||||
list2 = list(string2)
|
||||
count_n = 0
|
||||
for i in range(len(list1)):
|
||||
if list1[i] != list2[i]:
|
||||
count_n += 1
|
||||
count_n = sum(item1 != item2 for item1, item2 in zip(list1, list2))
|
||||
return count_n == count
|
||||
|
||||
|
||||
@ -92,40 +89,34 @@ def selection(chart: list[list[int]], prime_implicants: list[str]) -> list[str]:
|
||||
temp = []
|
||||
select = [0] * len(chart)
|
||||
for i in range(len(chart[0])):
|
||||
count = 0
|
||||
rem = -1
|
||||
for j in range(len(chart)):
|
||||
if chart[j][i] == 1:
|
||||
count += 1
|
||||
rem = j
|
||||
count = sum(row[i] == 1 for row in chart)
|
||||
if count == 1:
|
||||
rem = max(j for j, row in enumerate(chart) if row[i] == 1)
|
||||
select[rem] = 1
|
||||
for i in range(len(select)):
|
||||
if select[i] == 1:
|
||||
for j in range(len(chart[0])):
|
||||
if chart[i][j] == 1:
|
||||
for k in range(len(chart)):
|
||||
chart[k][j] = 0
|
||||
temp.append(prime_implicants[i])
|
||||
for i, item in enumerate(select):
|
||||
if item != 1:
|
||||
continue
|
||||
for j in range(len(chart[0])):
|
||||
if chart[i][j] != 1:
|
||||
continue
|
||||
for row in chart:
|
||||
row[j] = 0
|
||||
temp.append(prime_implicants[i])
|
||||
while True:
|
||||
max_n = 0
|
||||
rem = -1
|
||||
count_n = 0
|
||||
for i in range(len(chart)):
|
||||
count_n = chart[i].count(1)
|
||||
if count_n > max_n:
|
||||
max_n = count_n
|
||||
rem = i
|
||||
counts = [chart[i].count(1) for i in range(len(chart))]
|
||||
max_n = max(counts)
|
||||
rem = counts.index(max_n)
|
||||
|
||||
if max_n == 0:
|
||||
return temp
|
||||
|
||||
temp.append(prime_implicants[rem])
|
||||
|
||||
for i in range(len(chart[0])):
|
||||
if chart[rem][i] == 1:
|
||||
for j in range(len(chart)):
|
||||
chart[j][i] = 0
|
||||
for j in range(len(chart[0])):
|
||||
if chart[rem][j] != 1:
|
||||
continue
|
||||
for i in range(len(chart)):
|
||||
chart[i][j] = 0
|
||||
|
||||
|
||||
def prime_implicant_chart(
|
||||
|
550
cellular_automata/wa_tor.py
Normal file
550
cellular_automata/wa_tor.py
Normal file
@ -0,0 +1,550 @@
|
||||
"""
|
||||
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
|
||||
|
||||
This solution aims to completely remove any systematic approach
|
||||
to the Wa-Tor planet, and utilise fully random methods.
|
||||
|
||||
The constants are a working set that allows the Wa-Tor planet
|
||||
to result in one of the three possible results.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from random import randint, shuffle
|
||||
from time import sleep
|
||||
from typing import Literal
|
||||
|
||||
WIDTH = 50 # Width of the Wa-Tor planet
|
||||
HEIGHT = 50 # Height of the Wa-Tor planet
|
||||
|
||||
PREY_INITIAL_COUNT = 30 # The initial number of prey entities
|
||||
PREY_REPRODUCTION_TIME = 5 # The chronons before reproducing
|
||||
|
||||
PREDATOR_INITIAL_COUNT = 50 # The initial number of predator entities
|
||||
# The initial energy value of predator entities
|
||||
PREDATOR_INITIAL_ENERGY_VALUE = 15
|
||||
# The energy value provided when consuming prey
|
||||
PREDATOR_FOOD_VALUE = 5
|
||||
PREDATOR_REPRODUCTION_TIME = 20 # The chronons before reproducing
|
||||
|
||||
MAX_ENTITIES = 500 # The max number of organisms on the board
|
||||
# The number of entities to delete from the unbalanced side
|
||||
DELETE_UNBALANCED_ENTITIES = 50
|
||||
|
||||
|
||||
class Entity:
|
||||
"""
|
||||
Represents an entity (either prey or predator).
|
||||
|
||||
>>> e = Entity(True, coords=(0, 0))
|
||||
>>> e.prey
|
||||
True
|
||||
>>> e.coords
|
||||
(0, 0)
|
||||
>>> e.alive
|
||||
True
|
||||
"""
|
||||
|
||||
def __init__(self, prey: bool, coords: tuple[int, int]) -> None:
|
||||
self.prey = prey
|
||||
# The (row, col) pos of the entity
|
||||
self.coords = coords
|
||||
|
||||
self.remaining_reproduction_time = (
|
||||
PREY_REPRODUCTION_TIME if prey else PREDATOR_REPRODUCTION_TIME
|
||||
)
|
||||
self.energy_value = None if prey is True else PREDATOR_INITIAL_ENERGY_VALUE
|
||||
self.alive = True
|
||||
|
||||
def reset_reproduction_time(self) -> None:
|
||||
"""
|
||||
>>> e = Entity(True, coords=(0, 0))
|
||||
>>> e.reset_reproduction_time()
|
||||
>>> e.remaining_reproduction_time == PREY_REPRODUCTION_TIME
|
||||
True
|
||||
>>> e = Entity(False, coords=(0, 0))
|
||||
>>> e.reset_reproduction_time()
|
||||
>>> e.remaining_reproduction_time == PREDATOR_REPRODUCTION_TIME
|
||||
True
|
||||
"""
|
||||
self.remaining_reproduction_time = (
|
||||
PREY_REPRODUCTION_TIME if self.prey is True else PREDATOR_REPRODUCTION_TIME
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
>>> Entity(prey=True, coords=(1, 1))
|
||||
Entity(prey=True, coords=(1, 1), remaining_reproduction_time=5)
|
||||
>>> Entity(prey=False, coords=(2, 1)) # doctest: +NORMALIZE_WHITESPACE
|
||||
Entity(prey=False, coords=(2, 1),
|
||||
remaining_reproduction_time=20, energy_value=15)
|
||||
"""
|
||||
repr_ = (
|
||||
f"Entity(prey={self.prey}, coords={self.coords}, "
|
||||
f"remaining_reproduction_time={self.remaining_reproduction_time}"
|
||||
)
|
||||
if self.energy_value is not None:
|
||||
repr_ += f", energy_value={self.energy_value}"
|
||||
return f"{repr_})"
|
||||
|
||||
|
||||
class WaTor:
|
||||
"""
|
||||
Represents the main Wa-Tor algorithm.
|
||||
|
||||
: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.
|
||||
|
||||
>>> wt = WaTor(10, 15)
|
||||
>>> wt.width
|
||||
10
|
||||
>>> wt.height
|
||||
15
|
||||
>>> len(wt.planet)
|
||||
15
|
||||
>>> len(wt.planet[0])
|
||||
10
|
||||
>>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT
|
||||
True
|
||||
"""
|
||||
|
||||
time_passed: Callable[["WaTor", int], None] | None
|
||||
|
||||
def __init__(self, width: int, height: int) -> None:
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.time_passed = None
|
||||
|
||||
self.planet: list[list[Entity | None]] = [[None] * width for _ in range(height)]
|
||||
|
||||
# Populate planet with predators and prey randomly
|
||||
for _ in range(PREY_INITIAL_COUNT):
|
||||
self.add_entity(prey=True)
|
||||
for _ in range(PREDATOR_INITIAL_COUNT):
|
||||
self.add_entity(prey=False)
|
||||
self.set_planet(self.planet)
|
||||
|
||||
def set_planet(self, planet: list[list[Entity | None]]) -> None:
|
||||
"""
|
||||
Ease of access for testing
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> planet = [
|
||||
... [None, None, None],
|
||||
... [None, Entity(True, coords=(1, 1)), None]
|
||||
... ]
|
||||
>>> wt.set_planet(planet)
|
||||
>>> wt.planet == planet
|
||||
True
|
||||
>>> wt.width
|
||||
3
|
||||
>>> wt.height
|
||||
2
|
||||
"""
|
||||
self.planet = planet
|
||||
self.width = len(planet[0])
|
||||
self.height = len(planet)
|
||||
|
||||
def add_entity(self, prey: bool) -> None:
|
||||
"""
|
||||
Adds an entity, making sure the entity does
|
||||
not override another entity
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> wt.set_planet([[None, None], [None, None]])
|
||||
>>> wt.add_entity(True)
|
||||
>>> len(wt.get_entities())
|
||||
1
|
||||
>>> wt.add_entity(False)
|
||||
>>> len(wt.get_entities())
|
||||
2
|
||||
"""
|
||||
while True:
|
||||
row, col = randint(0, self.height - 1), randint(0, self.width - 1)
|
||||
if self.planet[row][col] is None:
|
||||
self.planet[row][col] = Entity(prey=prey, coords=(row, col))
|
||||
return
|
||||
|
||||
def get_entities(self) -> list[Entity]:
|
||||
"""
|
||||
Returns a list of all the entities within the planet.
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> len(wt.get_entities()) == PREDATOR_INITIAL_COUNT + PREY_INITIAL_COUNT
|
||||
True
|
||||
"""
|
||||
return [entity for column in self.planet for entity in column if entity]
|
||||
|
||||
def balance_predators_and_prey(self) -> None:
|
||||
"""
|
||||
Balances predators and preys so that prey
|
||||
can not dominate the predators, blocking up
|
||||
space for them to reproduce.
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> for i in range(2000):
|
||||
... row, col = i // HEIGHT, i % WIDTH
|
||||
... wt.planet[row][col] = Entity(True, coords=(row, col))
|
||||
>>> entities = len(wt.get_entities())
|
||||
>>> wt.balance_predators_and_prey()
|
||||
>>> len(wt.get_entities()) == entities
|
||||
False
|
||||
"""
|
||||
entities = self.get_entities()
|
||||
shuffle(entities)
|
||||
|
||||
if len(entities) >= MAX_ENTITIES - MAX_ENTITIES / 10:
|
||||
prey = [entity for entity in entities if entity.prey]
|
||||
predators = [entity for entity in entities if not entity.prey]
|
||||
|
||||
prey_count, predator_count = len(prey), len(predators)
|
||||
|
||||
entities_to_purge = (
|
||||
prey[:DELETE_UNBALANCED_ENTITIES]
|
||||
if prey_count > predator_count
|
||||
else predators[:DELETE_UNBALANCED_ENTITIES]
|
||||
)
|
||||
for entity in entities_to_purge:
|
||||
self.planet[entity.coords[0]][entity.coords[1]] = None
|
||||
|
||||
def get_surrounding_prey(self, entity: Entity) -> list[Entity]:
|
||||
"""
|
||||
Returns all the prey entities around (N, S, E, W) a predator entity.
|
||||
|
||||
Subtly different to the try_to_move_to_unoccupied square.
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> wt.set_planet([
|
||||
... [None, Entity(True, (0, 1)), None],
|
||||
... [None, Entity(False, (1, 1)), None],
|
||||
... [None, Entity(True, (2, 1)), None]])
|
||||
>>> wt.get_surrounding_prey(
|
||||
... Entity(False, (1, 1))) # doctest: +NORMALIZE_WHITESPACE
|
||||
[Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5),
|
||||
Entity(prey=True, coords=(2, 1), remaining_reproduction_time=5)]
|
||||
>>> wt.set_planet([[Entity(False, (0, 0))]])
|
||||
>>> wt.get_surrounding_prey(Entity(False, (0, 0)))
|
||||
[]
|
||||
>>> wt.set_planet([
|
||||
... [Entity(True, (0, 0)), Entity(False, (1, 0)), Entity(False, (2, 0))],
|
||||
... [None, Entity(False, (1, 1)), Entity(True, (2, 1))],
|
||||
... [None, None, None]])
|
||||
>>> wt.get_surrounding_prey(Entity(False, (1, 0)))
|
||||
[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5)]
|
||||
"""
|
||||
row, col = entity.coords
|
||||
adjacent: list[tuple[int, int]] = [
|
||||
(row - 1, col), # North
|
||||
(row + 1, col), # South
|
||||
(row, col - 1), # West
|
||||
(row, col + 1), # East
|
||||
]
|
||||
|
||||
return [
|
||||
ent
|
||||
for r, c in adjacent
|
||||
if 0 <= r < self.height
|
||||
and 0 <= c < self.width
|
||||
and (ent := self.planet[r][c]) is not None
|
||||
and ent.prey
|
||||
]
|
||||
|
||||
def move_and_reproduce(
|
||||
self, entity: Entity, direction_orders: list[Literal["N", "E", "S", "W"]]
|
||||
) -> None:
|
||||
"""
|
||||
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
|
||||
equal to 0, then a new prey or predator can also be created
|
||||
in the previous square.
|
||||
|
||||
:param direction_orders: Ordered list (like priority queue) depicting
|
||||
order to attempt to move. Removes any systematic
|
||||
approach of checking neighbouring squares.
|
||||
|
||||
>>> planet = [
|
||||
... [None, None, None],
|
||||
... [None, Entity(True, coords=(1, 1)), None],
|
||||
... [None, None, None]
|
||||
... ]
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> wt.set_planet(planet)
|
||||
>>> wt.move_and_reproduce(Entity(True, coords=(1, 1)), direction_orders=["N"])
|
||||
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
|
||||
[[None, Entity(prey=True, coords=(0, 1), remaining_reproduction_time=4), None],
|
||||
[None, None, None],
|
||||
[None, None, None]]
|
||||
>>> wt.planet[0][0] = Entity(True, coords=(0, 0))
|
||||
>>> wt.move_and_reproduce(Entity(True, coords=(0, 1)),
|
||||
... direction_orders=["N", "W", "E", "S"])
|
||||
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
|
||||
[[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), None,
|
||||
Entity(prey=True, coords=(0, 2), remaining_reproduction_time=4)],
|
||||
[None, None, None],
|
||||
[None, None, None]]
|
||||
>>> wt.planet[0][1] = wt.planet[0][2]
|
||||
>>> wt.planet[0][2] = None
|
||||
>>> wt.move_and_reproduce(Entity(True, coords=(0, 1)),
|
||||
... direction_orders=["N", "W", "S", "E"])
|
||||
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
|
||||
[[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5), None, None],
|
||||
[None, Entity(prey=True, coords=(1, 1), remaining_reproduction_time=4), None],
|
||||
[None, None, None]]
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> reproducable_entity = Entity(False, coords=(0, 1))
|
||||
>>> reproducable_entity.remaining_reproduction_time = 0
|
||||
>>> wt.planet = [[None, reproducable_entity]]
|
||||
>>> wt.move_and_reproduce(reproducable_entity,
|
||||
... direction_orders=["N", "W", "S", "E"])
|
||||
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
|
||||
[[Entity(prey=False, coords=(0, 0),
|
||||
remaining_reproduction_time=20, energy_value=15),
|
||||
Entity(prey=False, coords=(0, 1), remaining_reproduction_time=20,
|
||||
energy_value=15)]]
|
||||
"""
|
||||
row, col = coords = entity.coords
|
||||
|
||||
adjacent_squares: dict[Literal["N", "E", "S", "W"], tuple[int, int]] = {
|
||||
"N": (row - 1, col), # North
|
||||
"S": (row + 1, col), # South
|
||||
"W": (row, col - 1), # West
|
||||
"E": (row, col + 1), # East
|
||||
}
|
||||
# Weight adjacent locations
|
||||
adjacent: list[tuple[int, int]] = []
|
||||
for order in direction_orders:
|
||||
adjacent.append(adjacent_squares[order])
|
||||
|
||||
for r, c in adjacent:
|
||||
if (
|
||||
0 <= r < self.height
|
||||
and 0 <= c < self.width
|
||||
and self.planet[r][c] is None
|
||||
):
|
||||
# Move entity to empty adjacent square
|
||||
self.planet[r][c] = entity
|
||||
self.planet[row][col] = None
|
||||
entity.coords = (r, c)
|
||||
break
|
||||
|
||||
# (2.) See if it possible to reproduce in previous square
|
||||
if coords != entity.coords and entity.remaining_reproduction_time <= 0:
|
||||
# Check if the entities on the planet is less than the max limit
|
||||
if len(self.get_entities()) < MAX_ENTITIES:
|
||||
# Reproduce in previous square
|
||||
self.planet[row][col] = Entity(prey=entity.prey, coords=coords)
|
||||
entity.reset_reproduction_time()
|
||||
else:
|
||||
entity.remaining_reproduction_time -= 1
|
||||
|
||||
def perform_prey_actions(
|
||||
self, entity: Entity, direction_orders: list[Literal["N", "E", "S", "W"]]
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> reproducable_entity = Entity(True, coords=(0, 1))
|
||||
>>> reproducable_entity.remaining_reproduction_time = 0
|
||||
>>> wt.planet = [[None, reproducable_entity]]
|
||||
>>> wt.perform_prey_actions(reproducable_entity,
|
||||
... direction_orders=["N", "W", "S", "E"])
|
||||
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
|
||||
[[Entity(prey=True, coords=(0, 0), remaining_reproduction_time=5),
|
||||
Entity(prey=True, coords=(0, 1), remaining_reproduction_time=5)]]
|
||||
"""
|
||||
self.move_and_reproduce(entity, direction_orders)
|
||||
|
||||
def perform_predator_actions(
|
||||
self,
|
||||
entity: Entity,
|
||||
occupied_by_prey_coords: tuple[int, int] | None,
|
||||
direction_orders: list[Literal["N", "E", "S", "W"]],
|
||||
) -> None:
|
||||
"""
|
||||
Performs the actions for a predator entity
|
||||
|
||||
: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.
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> wt.set_planet([[Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1))]])
|
||||
>>> wt.perform_predator_actions(Entity(False, coords=(0, 1)), (0, 0), [])
|
||||
>>> wt.planet # doctest: +NORMALIZE_WHITESPACE
|
||||
[[Entity(prey=False, coords=(0, 0),
|
||||
remaining_reproduction_time=20, energy_value=19), None]]
|
||||
"""
|
||||
assert entity.energy_value is not None # [type checking]
|
||||
|
||||
# (3.) If the entity has 0 energy, it will die
|
||||
if entity.energy_value == 0:
|
||||
self.planet[entity.coords[0]][entity.coords[1]] = None
|
||||
return
|
||||
|
||||
# (1.) Move to entity if possible
|
||||
if occupied_by_prey_coords is not None:
|
||||
# Kill the prey
|
||||
prey = self.planet[occupied_by_prey_coords[0]][occupied_by_prey_coords[1]]
|
||||
assert prey is not None
|
||||
prey.alive = False
|
||||
|
||||
# Move onto prey
|
||||
self.planet[occupied_by_prey_coords[0]][occupied_by_prey_coords[1]] = entity
|
||||
self.planet[entity.coords[0]][entity.coords[1]] = None
|
||||
|
||||
entity.coords = occupied_by_prey_coords
|
||||
# (4.) Eats the prey and earns energy
|
||||
entity.energy_value += PREDATOR_FOOD_VALUE
|
||||
else:
|
||||
# (5.) If it has survived the certain number of chronons it will also
|
||||
# reproduce in this function
|
||||
self.move_and_reproduce(entity, direction_orders)
|
||||
|
||||
# (2.) Each chronon, the predator is deprived of a unit of energy
|
||||
entity.energy_value -= 1
|
||||
|
||||
def run(self, *, iteration_count: int) -> None:
|
||||
"""
|
||||
Emulate time passing by looping iteration_count times
|
||||
|
||||
>>> wt = WaTor(WIDTH, HEIGHT)
|
||||
>>> wt.run(iteration_count=PREDATOR_INITIAL_ENERGY_VALUE - 1)
|
||||
>>> len(list(filter(lambda entity: entity.prey is False,
|
||||
... wt.get_entities()))) >= PREDATOR_INITIAL_COUNT
|
||||
True
|
||||
"""
|
||||
for iter_num in range(iteration_count):
|
||||
# Generate list of all entities in order to randomly
|
||||
# pop an entity at a time to simulate true randomness
|
||||
# This removes the systematic approach of iterating
|
||||
# through each entity width by height
|
||||
all_entities = self.get_entities()
|
||||
|
||||
for __ in range(len(all_entities)):
|
||||
entity = all_entities.pop(randint(0, len(all_entities) - 1))
|
||||
if entity.alive is False:
|
||||
continue
|
||||
|
||||
directions: list[Literal["N", "E", "S", "W"]] = ["N", "E", "S", "W"]
|
||||
shuffle(directions) # Randomly shuffle directions
|
||||
|
||||
if entity.prey:
|
||||
self.perform_prey_actions(entity, directions)
|
||||
else:
|
||||
# Create list of surrounding prey
|
||||
surrounding_prey = self.get_surrounding_prey(entity)
|
||||
surrounding_prey_coords = None
|
||||
|
||||
if surrounding_prey:
|
||||
# Again, randomly shuffle directions
|
||||
shuffle(surrounding_prey)
|
||||
surrounding_prey_coords = surrounding_prey[0].coords
|
||||
|
||||
self.perform_predator_actions(
|
||||
entity, surrounding_prey_coords, directions
|
||||
)
|
||||
|
||||
# Balance out the predators and prey
|
||||
self.balance_predators_and_prey()
|
||||
|
||||
if self.time_passed is not None:
|
||||
# Call time_passed function for Wa-Tor planet
|
||||
# visualisation in a terminal or a graph.
|
||||
self.time_passed(self, iter_num)
|
||||
|
||||
|
||||
def visualise(wt: WaTor, iter_number: int, *, colour: bool = True) -> None:
|
||||
"""
|
||||
Visually displays the Wa-Tor planet using
|
||||
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
|
||||
|
||||
>>> wt = WaTor(30, 30)
|
||||
>>> wt.set_planet([
|
||||
... [Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1)), None],
|
||||
... [Entity(False, coords=(1, 0)), None, Entity(False, coords=(1, 2))],
|
||||
... [None, Entity(True, coords=(2, 1)), None]
|
||||
... ])
|
||||
>>> visualise(wt, 0, colour=False) # doctest: +NORMALIZE_WHITESPACE
|
||||
# x .
|
||||
x . x
|
||||
. # .
|
||||
<BLANKLINE>
|
||||
Iteration: 0 | Prey count: 2 | Predator count: 3 |
|
||||
"""
|
||||
if colour:
|
||||
__import__("os").system("")
|
||||
print("\x1b[0;0H\x1b[2J\x1b[?25l")
|
||||
|
||||
reprint = "\x1b[0;0H" if colour else ""
|
||||
ansi_colour_end = "\x1b[0m " if colour else " "
|
||||
|
||||
planet = wt.planet
|
||||
output = ""
|
||||
|
||||
# Iterate over every entity in the planet
|
||||
for row in planet:
|
||||
for entity in row:
|
||||
if entity is None:
|
||||
output += " . "
|
||||
else:
|
||||
if colour is True:
|
||||
output += (
|
||||
"\x1b[38;2;96;241;151m"
|
||||
if entity.prey
|
||||
else "\x1b[38;2;255;255;15m"
|
||||
)
|
||||
output += f" {'#' if entity.prey else 'x'}{ansi_colour_end}"
|
||||
|
||||
output += "\n"
|
||||
|
||||
entities = wt.get_entities()
|
||||
prey_count = sum(entity.prey for entity in entities)
|
||||
|
||||
print(
|
||||
f"{output}\n Iteration: {iter_number} | Prey count: {prey_count} | "
|
||||
f"Predator count: {len(entities) - prey_count} | {reprint}"
|
||||
)
|
||||
# Block the thread to be able to visualise seeing the algorithm
|
||||
sleep(0.05)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
wt = WaTor(WIDTH, HEIGHT)
|
||||
wt.time_passed = visualise
|
||||
wt.run(iteration_count=100_000)
|
@ -2,8 +2,7 @@ import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
from . import cryptomath_module as cryptoMath # noqa: N812
|
||||
from . import rabin_miller as rabinMiller # noqa: N812
|
||||
from . import cryptomath_module, rabin_miller
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@ -13,20 +12,26 @@ def main() -> None:
|
||||
|
||||
|
||||
def generate_key(key_size: int) -> tuple[tuple[int, int], tuple[int, int]]:
|
||||
print("Generating prime p...")
|
||||
p = rabinMiller.generate_large_prime(key_size)
|
||||
print("Generating prime q...")
|
||||
q = rabinMiller.generate_large_prime(key_size)
|
||||
"""
|
||||
>>> random.seed(0) # for repeatability
|
||||
>>> public_key, private_key = generate_key(8)
|
||||
>>> public_key
|
||||
(26569, 239)
|
||||
>>> private_key
|
||||
(26569, 2855)
|
||||
"""
|
||||
p = rabin_miller.generate_large_prime(key_size)
|
||||
q = rabin_miller.generate_large_prime(key_size)
|
||||
n = p * q
|
||||
|
||||
print("Generating e that is relatively prime to (p - 1) * (q - 1)...")
|
||||
# Generate e that is relatively prime to (p - 1) * (q - 1)
|
||||
while True:
|
||||
e = random.randrange(2 ** (key_size - 1), 2 ** (key_size))
|
||||
if cryptoMath.gcd(e, (p - 1) * (q - 1)) == 1:
|
||||
if cryptomath_module.gcd(e, (p - 1) * (q - 1)) == 1:
|
||||
break
|
||||
|
||||
print("Calculating d that is mod inverse of e...")
|
||||
d = cryptoMath.find_mod_inverse(e, (p - 1) * (q - 1))
|
||||
# Calculate d that is mod inverse of e
|
||||
d = cryptomath_module.find_mod_inverse(e, (p - 1) * (q - 1))
|
||||
|
||||
public_key = (n, e)
|
||||
private_key = (n, d)
|
||||
|
@ -32,13 +32,13 @@ def main() -> None:
|
||||
letter_code = random_chars(32)
|
||||
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])
|
||||
cv2.imwrite(f"{file_root}.jpg", image, [cv2.IMWRITE_JPEG_QUALITY, 85])
|
||||
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]}"
|
||||
annos_list.append(obj)
|
||||
with open(f"/{file_root}.txt", "w") as outfile:
|
||||
with open(f"{file_root}.txt", "w") as outfile:
|
||||
outfile.write("\n".join(line for line in annos_list))
|
||||
|
||||
|
||||
|
@ -1,13 +1,4 @@
|
||||
def octal_to_binary(octal_number):
|
||||
"""
|
||||
Convert an octal number to binary.
|
||||
|
||||
Args:
|
||||
octal_number (str): The octal number as a string.
|
||||
|
||||
Returns:
|
||||
str: The binary representation of the octal number.
|
||||
"""
|
||||
def octal_to_binary(octal_number: str) -> str:
|
||||
binary_number = ""
|
||||
octal_digits = "01234567"
|
||||
|
||||
|
@ -1,5 +1,62 @@
|
||||
"""
|
||||
r"""
|
||||
A binary search Tree
|
||||
|
||||
Example
|
||||
8
|
||||
/ \
|
||||
3 10
|
||||
/ \ \
|
||||
1 6 14
|
||||
/ \ /
|
||||
4 7 13
|
||||
|
||||
>>> t = BinarySearchTree()
|
||||
>>> t.insert(8, 3, 6, 1, 10, 14, 13, 4, 7)
|
||||
>>> print(" ".join(repr(i.value) for i in t.traversal_tree()))
|
||||
8 3 1 6 4 7 10 14 13
|
||||
>>> print(" ".join(repr(i.value) for i in t.traversal_tree(postorder)))
|
||||
1 4 7 6 3 13 14 10 8
|
||||
>>> t.remove(20)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Value 20 not found
|
||||
>>> BinarySearchTree().search(6)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: Warning: Tree is empty! please use another.
|
||||
|
||||
Other example:
|
||||
|
||||
>>> testlist = (8, 3, 6, 1, 10, 14, 13, 4, 7)
|
||||
>>> t = BinarySearchTree()
|
||||
>>> for i in testlist:
|
||||
... t.insert(i)
|
||||
|
||||
Prints all the elements of the list in order traversal
|
||||
>>> print(t)
|
||||
{'8': ({'3': (1, {'6': (4, 7)})}, {'10': (None, {'14': (13, None)})})}
|
||||
|
||||
Test existence
|
||||
>>> t.search(6) is not None
|
||||
True
|
||||
>>> t.search(-1) is not None
|
||||
False
|
||||
|
||||
>>> t.search(6).is_right
|
||||
True
|
||||
>>> t.search(1).is_right
|
||||
False
|
||||
|
||||
>>> t.get_max().value
|
||||
14
|
||||
>>> t.get_min().value
|
||||
1
|
||||
>>> t.empty()
|
||||
False
|
||||
>>> for i in testlist:
|
||||
... t.remove(i)
|
||||
>>> t.empty()
|
||||
True
|
||||
"""
|
||||
|
||||
from collections.abc import Iterable
|
||||
@ -20,6 +77,10 @@ class Node:
|
||||
return str(self.value)
|
||||
return pformat({f"{self.value}": (self.left, self.right)}, indent=1)
|
||||
|
||||
@property
|
||||
def is_right(self) -> bool:
|
||||
return self.parent is not None and self is self.parent.right
|
||||
|
||||
|
||||
class BinarySearchTree:
|
||||
def __init__(self, root: Node | None = None):
|
||||
@ -35,18 +96,13 @@ class BinarySearchTree:
|
||||
if new_children is not None: # reset its kids
|
||||
new_children.parent = node.parent
|
||||
if node.parent is not None: # reset its parent
|
||||
if self.is_right(node): # If it is the right children
|
||||
if node.is_right: # If it is the right child
|
||||
node.parent.right = new_children
|
||||
else:
|
||||
node.parent.left = new_children
|
||||
else:
|
||||
self.root = new_children
|
||||
|
||||
def is_right(self, node: Node) -> bool:
|
||||
if node.parent and node.parent.right:
|
||||
return node == node.parent.right
|
||||
return False
|
||||
|
||||
def empty(self) -> bool:
|
||||
return self.root is None
|
||||
|
||||
@ -119,22 +175,26 @@ class BinarySearchTree:
|
||||
return node
|
||||
|
||||
def remove(self, value: int) -> None:
|
||||
node = self.search(value) # Look for the node with that label
|
||||
if node is not None:
|
||||
if node.left is None and node.right is None: # If it has no children
|
||||
self.__reassign_nodes(node, None)
|
||||
elif node.left is None: # Has only right children
|
||||
self.__reassign_nodes(node, node.right)
|
||||
elif node.right is None: # Has only left children
|
||||
self.__reassign_nodes(node, node.left)
|
||||
else:
|
||||
tmp_node = self.get_max(
|
||||
node.left
|
||||
) # Gets the max value of the left branch
|
||||
self.remove(tmp_node.value) # type: ignore
|
||||
node.value = (
|
||||
tmp_node.value # type: ignore
|
||||
) # Assigns the value to the node to delete and keep tree structure
|
||||
# Look for the node with that label
|
||||
node = self.search(value)
|
||||
if node is None:
|
||||
msg = f"Value {value} not found"
|
||||
raise ValueError(msg)
|
||||
|
||||
if node.left is None and node.right is None: # If it has no children
|
||||
self.__reassign_nodes(node, None)
|
||||
elif node.left is None: # Has only right children
|
||||
self.__reassign_nodes(node, node.right)
|
||||
elif node.right is None: # Has only left children
|
||||
self.__reassign_nodes(node, node.left)
|
||||
else:
|
||||
predecessor = self.get_max(
|
||||
node.left
|
||||
) # Gets the max value of the left branch
|
||||
self.remove(predecessor.value) # type: ignore
|
||||
node.value = (
|
||||
predecessor.value # type: ignore
|
||||
) # Assigns the value to the node to delete and keep tree structure
|
||||
|
||||
def preorder_traverse(self, node: Node | None) -> Iterable:
|
||||
if node is not None:
|
||||
@ -177,55 +237,6 @@ def postorder(curr_node: Node | None) -> list[Node]:
|
||||
return node_list
|
||||
|
||||
|
||||
def binary_search_tree() -> None:
|
||||
r"""
|
||||
Example
|
||||
8
|
||||
/ \
|
||||
3 10
|
||||
/ \ \
|
||||
1 6 14
|
||||
/ \ /
|
||||
4 7 13
|
||||
|
||||
>>> t = BinarySearchTree()
|
||||
>>> t.insert(8, 3, 6, 1, 10, 14, 13, 4, 7)
|
||||
>>> print(" ".join(repr(i.value) for i in t.traversal_tree()))
|
||||
8 3 1 6 4 7 10 14 13
|
||||
>>> print(" ".join(repr(i.value) for i in t.traversal_tree(postorder)))
|
||||
1 4 7 6 3 13 14 10 8
|
||||
>>> BinarySearchTree().search(6)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: Warning: Tree is empty! please use another.
|
||||
"""
|
||||
testlist = (8, 3, 6, 1, 10, 14, 13, 4, 7)
|
||||
t = BinarySearchTree()
|
||||
for i in testlist:
|
||||
t.insert(i)
|
||||
|
||||
# Prints all the elements of the list in order traversal
|
||||
print(t)
|
||||
|
||||
if t.search(6) is not None:
|
||||
print("The value 6 exists")
|
||||
else:
|
||||
print("The value 6 doesn't exist")
|
||||
|
||||
if t.search(-1) is not None:
|
||||
print("The value -1 exists")
|
||||
else:
|
||||
print("The value -1 doesn't exist")
|
||||
|
||||
if not t.empty():
|
||||
print("Max Value: ", t.get_max().value) # type: ignore
|
||||
print("Min Value: ", t.get_min().value) # type: ignore
|
||||
|
||||
for i in testlist:
|
||||
t.remove(i)
|
||||
print(t)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
|
@ -21,6 +21,7 @@ def rgb2gray(rgb: np.array) -> np.array:
|
||||
def gray2binary(gray: np.array) -> np.array:
|
||||
"""
|
||||
Return binary image from gray image
|
||||
|
||||
>>> gray2binary(np.array([[127, 255, 0]]))
|
||||
array([[False, True, False]])
|
||||
>>> gray2binary(np.array([[0]]))
|
||||
|
@ -10,12 +10,12 @@ def get_rotation(
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Get image rotation
|
||||
:param img: np.array
|
||||
:param img: np.ndarray
|
||||
:param pt1: 3x2 list
|
||||
:param pt2: 3x2 list
|
||||
:param rows: columns image shape
|
||||
:param cols: rows image shape
|
||||
:return: np.array
|
||||
:return: np.ndarray
|
||||
"""
|
||||
matrix = cv2.getAffineTransform(pt1, pt2)
|
||||
return cv2.warpAffine(img, matrix, (rows, cols))
|
||||
|
97
dynamic_programming/regex_match.py
Normal file
97
dynamic_programming/regex_match.py
Normal file
@ -0,0 +1,97 @@
|
||||
"""
|
||||
Regex matching check if a text matches pattern or not.
|
||||
Pattern:
|
||||
'.' Matches any single character.
|
||||
'*' 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:
|
||||
"""
|
||||
Recursive matching algorithm.
|
||||
|
||||
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.
|
||||
|
||||
>>> recursive_match('abc', 'a.c')
|
||||
True
|
||||
>>> recursive_match('abc', 'af*.c')
|
||||
True
|
||||
>>> recursive_match('abc', 'a.c*')
|
||||
True
|
||||
>>> recursive_match('abc', 'a.c*d')
|
||||
False
|
||||
>>> recursive_match('aa', '.*')
|
||||
True
|
||||
"""
|
||||
if not pattern:
|
||||
return not text
|
||||
|
||||
if not text:
|
||||
return pattern[-1] == "*" and recursive_match(text, pattern[:-2])
|
||||
|
||||
if text[-1] == pattern[-1] or pattern[-1] == ".":
|
||||
return recursive_match(text[:-1], pattern[:-1])
|
||||
|
||||
if pattern[-1] == "*":
|
||||
return recursive_match(text[:-1], pattern) or recursive_match(
|
||||
text, pattern[:-2]
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def dp_match(text: str, pattern: str) -> bool:
|
||||
"""
|
||||
Dynamic programming matching algorithm.
|
||||
|
||||
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.
|
||||
|
||||
>>> dp_match('abc', 'a.c')
|
||||
True
|
||||
>>> dp_match('abc', 'af*.c')
|
||||
True
|
||||
>>> dp_match('abc', 'a.c*')
|
||||
True
|
||||
>>> dp_match('abc', 'a.c*d')
|
||||
False
|
||||
>>> dp_match('aa', '.*')
|
||||
True
|
||||
"""
|
||||
m = len(text)
|
||||
n = len(pattern)
|
||||
dp = [[False for _ in range(n + 1)] for _ in range(m + 1)]
|
||||
dp[0][0] = True
|
||||
|
||||
for j in range(1, n + 1):
|
||||
dp[0][j] = pattern[j - 1] == "*" and dp[0][j - 2]
|
||||
|
||||
for i in range(1, m + 1):
|
||||
for j in range(1, n + 1):
|
||||
if pattern[j - 1] in {".", text[i - 1]}:
|
||||
dp[i][j] = dp[i - 1][j - 1]
|
||||
elif pattern[j - 1] == "*":
|
||||
dp[i][j] = dp[i][j - 2]
|
||||
if pattern[j - 2] in {".", text[i - 1]}:
|
||||
dp[i][j] |= dp[i - 1][j]
|
||||
else:
|
||||
dp[i][j] = False
|
||||
|
||||
return dp[m][n]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
24
dynamic_programming/tribonacci.py
Normal file
24
dynamic_programming/tribonacci.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Tribonacci sequence using Dynamic Programming
|
||||
|
||||
|
||||
def tribonacci(num: int) -> list[int]:
|
||||
"""
|
||||
Given a number, return first n Tribonacci Numbers.
|
||||
>>> tribonacci(5)
|
||||
[0, 0, 1, 1, 2]
|
||||
>>> tribonacci(8)
|
||||
[0, 0, 1, 1, 2, 4, 7, 13]
|
||||
"""
|
||||
dp = [0] * num
|
||||
dp[2] = 1
|
||||
|
||||
for i in range(3, num):
|
||||
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
|
||||
|
||||
return dp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
@ -6,14 +6,32 @@ from __future__ import annotations
|
||||
|
||||
Path = list[tuple[int, int]]
|
||||
|
||||
grid = [
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0],
|
||||
# 0's are free path whereas 1's are obstacles
|
||||
TEST_GRIDS = [
|
||||
[
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0],
|
||||
],
|
||||
[
|
||||
[0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 1],
|
||||
[0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 0, 0, 1, 0, 0],
|
||||
[1, 0, 0, 1, 1, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
],
|
||||
[
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 1],
|
||||
[1, 0, 0, 1, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
],
|
||||
]
|
||||
|
||||
delta = ([-1, 0], [0, -1], [1, 0], [0, 1]) # up, left, down, right
|
||||
@ -65,10 +83,14 @@ class Node:
|
||||
def __lt__(self, other) -> bool:
|
||||
return self.f_cost < other.f_cost
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return self.pos == other.pos
|
||||
|
||||
|
||||
class GreedyBestFirst:
|
||||
"""
|
||||
>>> gbf = GreedyBestFirst((0, 0), (len(grid) - 1, len(grid[0]) - 1))
|
||||
>>> grid = TEST_GRIDS[2]
|
||||
>>> gbf = GreedyBestFirst(grid, (0, 0), (len(grid) - 1, len(grid[0]) - 1))
|
||||
>>> [x.pos for x in gbf.get_successors(gbf.start)]
|
||||
[(1, 0), (0, 1)]
|
||||
>>> (gbf.start.pos_y + delta[3][0], gbf.start.pos_x + delta[3][1])
|
||||
@ -78,11 +100,14 @@ class GreedyBestFirst:
|
||||
>>> gbf.retrace_path(gbf.start)
|
||||
[(0, 0)]
|
||||
>>> gbf.search() # doctest: +NORMALIZE_WHITESPACE
|
||||
[(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (4, 1), (5, 1), (6, 1),
|
||||
(6, 2), (6, 3), (5, 3), (5, 4), (5, 5), (6, 5), (6, 6)]
|
||||
[(0, 0), (1, 0), (2, 0), (2, 1), (3, 1), (4, 1), (4, 2), (4, 3),
|
||||
(4, 4)]
|
||||
"""
|
||||
|
||||
def __init__(self, start: tuple[int, int], goal: tuple[int, int]):
|
||||
def __init__(
|
||||
self, grid: list[list[int]], start: tuple[int, int], goal: tuple[int, int]
|
||||
):
|
||||
self.grid = grid
|
||||
self.start = Node(start[1], start[0], goal[1], goal[0], 0, None)
|
||||
self.target = Node(goal[1], goal[0], goal[1], goal[0], 99999, None)
|
||||
|
||||
@ -114,14 +139,6 @@ class GreedyBestFirst:
|
||||
|
||||
if child_node not in self.open_nodes:
|
||||
self.open_nodes.append(child_node)
|
||||
else:
|
||||
# retrieve the best current path
|
||||
better_node = self.open_nodes.pop(self.open_nodes.index(child_node))
|
||||
|
||||
if child_node.g_cost < better_node.g_cost:
|
||||
self.open_nodes.append(child_node)
|
||||
else:
|
||||
self.open_nodes.append(better_node)
|
||||
|
||||
if not self.reached:
|
||||
return [self.start.pos]
|
||||
@ -131,28 +148,22 @@ class GreedyBestFirst:
|
||||
"""
|
||||
Returns a list of successors (both in the grid and free spaces)
|
||||
"""
|
||||
successors = []
|
||||
for action in delta:
|
||||
pos_x = parent.pos_x + action[1]
|
||||
pos_y = parent.pos_y + action[0]
|
||||
|
||||
if not (0 <= pos_x <= len(grid[0]) - 1 and 0 <= pos_y <= len(grid) - 1):
|
||||
continue
|
||||
|
||||
if grid[pos_y][pos_x] != 0:
|
||||
continue
|
||||
|
||||
successors.append(
|
||||
Node(
|
||||
pos_x,
|
||||
pos_y,
|
||||
self.target.pos_y,
|
||||
self.target.pos_x,
|
||||
parent.g_cost + 1,
|
||||
parent,
|
||||
)
|
||||
return [
|
||||
Node(
|
||||
pos_x,
|
||||
pos_y,
|
||||
self.target.pos_x,
|
||||
self.target.pos_y,
|
||||
parent.g_cost + 1,
|
||||
parent,
|
||||
)
|
||||
return successors
|
||||
for action in delta
|
||||
if (
|
||||
0 <= (pos_x := parent.pos_x + action[1]) < len(self.grid[0])
|
||||
and 0 <= (pos_y := parent.pos_y + action[0]) < len(self.grid)
|
||||
and self.grid[pos_y][pos_x] == 0
|
||||
)
|
||||
]
|
||||
|
||||
def retrace_path(self, node: Node | None) -> Path:
|
||||
"""
|
||||
@ -168,18 +179,21 @@ class GreedyBestFirst:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init = (0, 0)
|
||||
goal = (len(grid) - 1, len(grid[0]) - 1)
|
||||
for elem in grid:
|
||||
print(elem)
|
||||
|
||||
print("------")
|
||||
|
||||
greedy_bf = GreedyBestFirst(init, goal)
|
||||
path = greedy_bf.search()
|
||||
if path:
|
||||
for pos_x, pos_y in path:
|
||||
grid[pos_x][pos_y] = 2
|
||||
for idx, grid in enumerate(TEST_GRIDS):
|
||||
print(f"==grid-{idx + 1}==")
|
||||
|
||||
init = (0, 0)
|
||||
goal = (len(grid) - 1, len(grid[0]) - 1)
|
||||
for elem in grid:
|
||||
print(elem)
|
||||
|
||||
print("------")
|
||||
|
||||
greedy_bf = GreedyBestFirst(grid, init, goal)
|
||||
path = greedy_bf.search()
|
||||
if path:
|
||||
for pos_x, pos_y in path:
|
||||
grid[pos_x][pos_y] = 2
|
||||
|
||||
for elem in grid:
|
||||
print(elem)
|
||||
|
@ -43,62 +43,43 @@ def points_to_polynomial(coordinates: list[list[int]]) -> str:
|
||||
|
||||
x = len(coordinates)
|
||||
|
||||
count_of_line = 0
|
||||
matrix: list[list[float]] = []
|
||||
# put the x and x to the power values in a matrix
|
||||
while count_of_line < x:
|
||||
count_in_line = 0
|
||||
a = coordinates[count_of_line][0]
|
||||
count_line: list[float] = []
|
||||
while count_in_line < x:
|
||||
count_line.append(a ** (x - (count_in_line + 1)))
|
||||
count_in_line += 1
|
||||
matrix.append(count_line)
|
||||
count_of_line += 1
|
||||
matrix: list[list[float]] = [
|
||||
[
|
||||
coordinates[count_of_line][0] ** (x - (count_in_line + 1))
|
||||
for count_in_line in range(x)
|
||||
]
|
||||
for count_of_line in range(x)
|
||||
]
|
||||
|
||||
count_of_line = 0
|
||||
# put the y values into a vector
|
||||
vector: list[float] = []
|
||||
while count_of_line < x:
|
||||
vector.append(coordinates[count_of_line][1])
|
||||
count_of_line += 1
|
||||
vector: list[float] = [coordinates[count_of_line][1] for count_of_line in range(x)]
|
||||
|
||||
count = 0
|
||||
|
||||
while count < x:
|
||||
zahlen = 0
|
||||
while zahlen < x:
|
||||
if count == zahlen:
|
||||
zahlen += 1
|
||||
if zahlen == x:
|
||||
break
|
||||
bruch = matrix[zahlen][count] / matrix[count][count]
|
||||
for count in range(x):
|
||||
for number in range(x):
|
||||
if count == number:
|
||||
continue
|
||||
fraction = matrix[number][count] / matrix[count][count]
|
||||
for counting_columns, item in enumerate(matrix[count]):
|
||||
# manipulating all the values in the matrix
|
||||
matrix[zahlen][counting_columns] -= item * bruch
|
||||
matrix[number][counting_columns] -= item * fraction
|
||||
# manipulating the values in the vector
|
||||
vector[zahlen] -= vector[count] * bruch
|
||||
zahlen += 1
|
||||
count += 1
|
||||
vector[number] -= vector[count] * fraction
|
||||
|
||||
count = 0
|
||||
# make solutions
|
||||
solution: list[str] = []
|
||||
while count < x:
|
||||
solution.append(str(vector[count] / matrix[count][count]))
|
||||
count += 1
|
||||
solution: list[str] = [
|
||||
str(vector[count] / matrix[count][count]) for count in range(x)
|
||||
]
|
||||
|
||||
count = 0
|
||||
solved = "f(x)="
|
||||
|
||||
while count < x:
|
||||
for count in range(x):
|
||||
remove_e: list[str] = solution[count].split("E")
|
||||
if len(remove_e) > 1:
|
||||
solution[count] = f"{remove_e[0]}*10^{remove_e[1]}"
|
||||
solved += f"x^{x - (count + 1)}*{solution[count]}"
|
||||
if count + 1 != x:
|
||||
solved += "+"
|
||||
count += 1
|
||||
|
||||
return solved
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
total_user,total_events,days
|
||||
total_users,total_events,days
|
||||
18231,0.0,1
|
||||
22621,1.0,2
|
||||
15675,0.0,3
|
||||
|
|
@ -1,6 +1,6 @@
|
||||
"""
|
||||
this is code for forecasting
|
||||
but i modified it and used it for safety checker of data
|
||||
but I modified it and used it for safety checker of data
|
||||
for ex: you have an online shop and for some reason some data are
|
||||
missing (the amount of data that u expected are not supposed to be)
|
||||
then we can use it
|
||||
@ -11,6 +11,8 @@ missing (the amount of data that u expected are not supposed to be)
|
||||
u can just adjust it for ur own purpose
|
||||
"""
|
||||
|
||||
from warnings import simplefilter
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from sklearn.preprocessing import Normalizer
|
||||
@ -45,8 +47,10 @@ def sarimax_predictor(train_user: list, train_match: list, test_match: list) ->
|
||||
>>> sarimax_predictor([4,2,6,8], [3,1,2,4], [2])
|
||||
6.6666671111109626
|
||||
"""
|
||||
# Suppress the User Warning raised by SARIMAX due to insufficient observations
|
||||
simplefilter("ignore", UserWarning)
|
||||
order = (1, 2, 1)
|
||||
seasonal_order = (1, 1, 0, 7)
|
||||
seasonal_order = (1, 1, 1, 7)
|
||||
model = SARIMAX(
|
||||
train_user, exog=train_match, order=order, seasonal_order=seasonal_order
|
||||
)
|
||||
@ -102,6 +106,10 @@ def data_safety_checker(list_vote: list, actual_result: float) -> bool:
|
||||
"""
|
||||
safe = 0
|
||||
not_safe = 0
|
||||
|
||||
if not isinstance(actual_result, float):
|
||||
raise TypeError("Actual result should be float. Value passed is a list")
|
||||
|
||||
for i in list_vote:
|
||||
if i > actual_result:
|
||||
safe = not_safe + 1
|
||||
@ -114,16 +122,11 @@ def data_safety_checker(list_vote: list, actual_result: float) -> bool:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# data_input_df = pd.read_csv("ex_data.csv", header=None)
|
||||
data_input = [[18231, 0.0, 1], [22621, 1.0, 2], [15675, 0.0, 3], [23583, 1.0, 4]]
|
||||
data_input_df = pd.DataFrame(
|
||||
data_input, columns=["total_user", "total_even", "days"]
|
||||
)
|
||||
|
||||
"""
|
||||
data column = total user in a day, how much online event held in one day,
|
||||
what day is that(sunday-saturday)
|
||||
"""
|
||||
data_input_df = pd.read_csv("ex_data.csv")
|
||||
|
||||
# start normalization
|
||||
normalize_df = Normalizer().fit_transform(data_input_df.values)
|
||||
@ -138,23 +141,23 @@ if __name__ == "__main__":
|
||||
x_test = x[len(x) - 1 :]
|
||||
|
||||
# for linear regression & sarimax
|
||||
trn_date = total_date[: len(total_date) - 1]
|
||||
trn_user = total_user[: len(total_user) - 1]
|
||||
trn_match = total_match[: len(total_match) - 1]
|
||||
train_date = total_date[: len(total_date) - 1]
|
||||
train_user = total_user[: len(total_user) - 1]
|
||||
train_match = total_match[: len(total_match) - 1]
|
||||
|
||||
tst_date = total_date[len(total_date) - 1 :]
|
||||
tst_user = total_user[len(total_user) - 1 :]
|
||||
tst_match = total_match[len(total_match) - 1 :]
|
||||
test_date = total_date[len(total_date) - 1 :]
|
||||
test_user = total_user[len(total_user) - 1 :]
|
||||
test_match = total_match[len(total_match) - 1 :]
|
||||
|
||||
# voting system with forecasting
|
||||
res_vote = [
|
||||
linear_regression_prediction(
|
||||
trn_date, trn_user, trn_match, tst_date, tst_match
|
||||
train_date, train_user, train_match, test_date, test_match
|
||||
),
|
||||
sarimax_predictor(trn_user, trn_match, tst_match),
|
||||
support_vector_regressor(x_train, x_test, trn_user),
|
||||
sarimax_predictor(train_user, train_match, test_match),
|
||||
support_vector_regressor(x_train, x_test, train_user),
|
||||
]
|
||||
|
||||
# check the safety of today's data
|
||||
not_str = "" if data_safety_checker(res_vote, tst_user) else "not "
|
||||
print("Today's data is {not_str}safe.")
|
||||
not_str = "" if data_safety_checker(res_vote, test_user[0]) else "not "
|
||||
print(f"Today's data is {not_str}safe.")
|
||||
|
@ -19,7 +19,9 @@ def median(nums: list) -> int | float:
|
||||
Returns:
|
||||
Median.
|
||||
"""
|
||||
sorted_list = sorted(nums)
|
||||
# The sorted function returns list[SupportsRichComparisonT@sorted]
|
||||
# which does not support `+`
|
||||
sorted_list: list[int] = sorted(nums)
|
||||
length = len(sorted_list)
|
||||
mid_index = length >> 1
|
||||
return (
|
||||
|
51
maths/continued_fraction.py
Normal file
51
maths/continued_fraction.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""
|
||||
Finding the continuous fraction for a rational number using python
|
||||
|
||||
https://en.wikipedia.org/wiki/Continued_fraction
|
||||
"""
|
||||
|
||||
|
||||
from fractions import Fraction
|
||||
|
||||
|
||||
def continued_fraction(num: Fraction) -> list[int]:
|
||||
"""
|
||||
:param num:
|
||||
Fraction of the number whose continued fractions to be found.
|
||||
Use Fraction(str(number)) for more accurate results due to
|
||||
float inaccuracies.
|
||||
|
||||
:return:
|
||||
The continued fraction of rational number.
|
||||
It is the all commas in the (n + 1)-tuple notation.
|
||||
|
||||
>>> continued_fraction(Fraction(2))
|
||||
[2]
|
||||
>>> continued_fraction(Fraction("3.245"))
|
||||
[3, 4, 12, 4]
|
||||
>>> continued_fraction(Fraction("2.25"))
|
||||
[2, 4]
|
||||
>>> continued_fraction(1/Fraction("2.25"))
|
||||
[0, 2, 4]
|
||||
>>> continued_fraction(Fraction("415/93"))
|
||||
[4, 2, 6, 7]
|
||||
"""
|
||||
numerator, denominator = num.as_integer_ratio()
|
||||
continued_fraction_list: list[int] = []
|
||||
while True:
|
||||
integer_part = int(numerator / denominator)
|
||||
continued_fraction_list.append(integer_part)
|
||||
numerator -= integer_part * denominator
|
||||
if numerator == 0:
|
||||
break
|
||||
numerator, denominator = denominator, numerator
|
||||
|
||||
return continued_fraction_list
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
print("Continued Fraction of 0.84375 is: ", continued_fraction(Fraction("0.84375")))
|
@ -5,7 +5,7 @@ import numpy as np
|
||||
|
||||
def euler_modified(
|
||||
ode_func: Callable, y0: float, x0: float, step_size: float, x_end: float
|
||||
) -> np.array:
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Calculate solution at each step to an ODE using Euler's Modified Method
|
||||
The Euler Method is straightforward to implement, but can't give accurate solutions.
|
||||
|
@ -1,23 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def find_max(nums: list[int | float]) -> int | float:
|
||||
def find_max_iterative(nums: list[int | float]) -> int | float:
|
||||
"""
|
||||
>>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]):
|
||||
... find_max(nums) == max(nums)
|
||||
... find_max_iterative(nums) == max(nums)
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
>>> find_max([2, 4, 9, 7, 19, 94, 5])
|
||||
>>> find_max_iterative([2, 4, 9, 7, 19, 94, 5])
|
||||
94
|
||||
>>> find_max([])
|
||||
>>> find_max_iterative([])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: find_max() arg is an empty sequence
|
||||
ValueError: find_max_iterative() arg is an empty sequence
|
||||
"""
|
||||
if len(nums) == 0:
|
||||
raise ValueError("find_max() arg is an empty sequence")
|
||||
raise ValueError("find_max_iterative() arg is an empty sequence")
|
||||
max_num = nums[0]
|
||||
for x in nums:
|
||||
if x > max_num:
|
||||
@ -25,6 +25,59 @@ def find_max(nums: list[int | float]) -> int | float:
|
||||
return max_num
|
||||
|
||||
|
||||
# Divide and Conquer algorithm
|
||||
def find_max_recursive(nums: list[int | float], left: int, right: int) -> int | float:
|
||||
"""
|
||||
find max value in list
|
||||
:param nums: contains elements
|
||||
:param left: index of first element
|
||||
:param right: index of last element
|
||||
:return: max in nums
|
||||
|
||||
>>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]):
|
||||
... find_max_recursive(nums, 0, len(nums) - 1) == max(nums)
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
>>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]
|
||||
>>> find_max_recursive(nums, 0, len(nums) - 1) == max(nums)
|
||||
True
|
||||
>>> find_max_recursive([], 0, 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: find_max_recursive() arg is an empty sequence
|
||||
>>> find_max_recursive(nums, 0, len(nums)) == max(nums)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
>>> find_max_recursive(nums, -len(nums), -1) == max(nums)
|
||||
True
|
||||
>>> find_max_recursive(nums, -len(nums) - 1, -1) == max(nums)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
"""
|
||||
if len(nums) == 0:
|
||||
raise ValueError("find_max_recursive() arg is an empty sequence")
|
||||
if (
|
||||
left >= len(nums)
|
||||
or left < -len(nums)
|
||||
or right >= len(nums)
|
||||
or right < -len(nums)
|
||||
):
|
||||
raise IndexError("list index out of range")
|
||||
if left == right:
|
||||
return nums[left]
|
||||
mid = (left + right) >> 1 # the middle
|
||||
left_max = find_max_recursive(nums, left, mid) # find max in range[left, mid]
|
||||
right_max = find_max_recursive(
|
||||
nums, mid + 1, right
|
||||
) # find max in range[mid + 1, right]
|
||||
|
||||
return left_max if left_max >= right_max else right_max
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
|
@ -1,58 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
# Divide and Conquer algorithm
|
||||
def find_max(nums: list[int | float], left: int, right: int) -> int | float:
|
||||
"""
|
||||
find max value in list
|
||||
:param nums: contains elements
|
||||
:param left: index of first element
|
||||
:param right: index of last element
|
||||
:return: max in nums
|
||||
|
||||
>>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]):
|
||||
... find_max(nums, 0, len(nums) - 1) == max(nums)
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
>>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]
|
||||
>>> find_max(nums, 0, len(nums) - 1) == max(nums)
|
||||
True
|
||||
>>> find_max([], 0, 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: find_max() arg is an empty sequence
|
||||
>>> find_max(nums, 0, len(nums)) == max(nums)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
>>> find_max(nums, -len(nums), -1) == max(nums)
|
||||
True
|
||||
>>> find_max(nums, -len(nums) - 1, -1) == max(nums)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
"""
|
||||
if len(nums) == 0:
|
||||
raise ValueError("find_max() arg is an empty sequence")
|
||||
if (
|
||||
left >= len(nums)
|
||||
or left < -len(nums)
|
||||
or right >= len(nums)
|
||||
or right < -len(nums)
|
||||
):
|
||||
raise IndexError("list index out of range")
|
||||
if left == right:
|
||||
return nums[left]
|
||||
mid = (left + right) >> 1 # the middle
|
||||
left_max = find_max(nums, left, mid) # find max in range[left, mid]
|
||||
right_max = find_max(nums, mid + 1, right) # find max in range[mid + 1, right]
|
||||
|
||||
return left_max if left_max >= right_max else right_max
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod(verbose=True)
|
@ -1,33 +1,86 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def find_min(nums: list[int | float]) -> int | float:
|
||||
def find_min_iterative(nums: list[int | float]) -> int | float:
|
||||
"""
|
||||
Find Minimum Number in a List
|
||||
:param nums: contains elements
|
||||
:return: min number in list
|
||||
|
||||
>>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]):
|
||||
... find_min(nums) == min(nums)
|
||||
... find_min_iterative(nums) == min(nums)
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
>>> find_min([0, 1, 2, 3, 4, 5, -3, 24, -56])
|
||||
>>> find_min_iterative([0, 1, 2, 3, 4, 5, -3, 24, -56])
|
||||
-56
|
||||
>>> find_min([])
|
||||
>>> find_min_iterative([])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: find_min() arg is an empty sequence
|
||||
ValueError: find_min_iterative() arg is an empty sequence
|
||||
"""
|
||||
if len(nums) == 0:
|
||||
raise ValueError("find_min() arg is an empty sequence")
|
||||
raise ValueError("find_min_iterative() arg is an empty sequence")
|
||||
min_num = nums[0]
|
||||
for num in nums:
|
||||
min_num = min(min_num, num)
|
||||
return min_num
|
||||
|
||||
|
||||
# Divide and Conquer algorithm
|
||||
def find_min_recursive(nums: list[int | float], left: int, right: int) -> int | float:
|
||||
"""
|
||||
find min value in list
|
||||
:param nums: contains elements
|
||||
:param left: index of first element
|
||||
:param right: index of last element
|
||||
:return: min in nums
|
||||
|
||||
>>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]):
|
||||
... find_min_recursive(nums, 0, len(nums) - 1) == min(nums)
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
>>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]
|
||||
>>> find_min_recursive(nums, 0, len(nums) - 1) == min(nums)
|
||||
True
|
||||
>>> find_min_recursive([], 0, 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: find_min_recursive() arg is an empty sequence
|
||||
>>> find_min_recursive(nums, 0, len(nums)) == min(nums)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
>>> find_min_recursive(nums, -len(nums), -1) == min(nums)
|
||||
True
|
||||
>>> find_min_recursive(nums, -len(nums) - 1, -1) == min(nums)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
"""
|
||||
if len(nums) == 0:
|
||||
raise ValueError("find_min_recursive() arg is an empty sequence")
|
||||
if (
|
||||
left >= len(nums)
|
||||
or left < -len(nums)
|
||||
or right >= len(nums)
|
||||
or right < -len(nums)
|
||||
):
|
||||
raise IndexError("list index out of range")
|
||||
if left == right:
|
||||
return nums[left]
|
||||
mid = (left + right) >> 1 # the middle
|
||||
left_min = find_min_recursive(nums, left, mid) # find min in range[left, mid]
|
||||
right_min = find_min_recursive(
|
||||
nums, mid + 1, right
|
||||
) # find min in range[mid + 1, right]
|
||||
|
||||
return left_min if left_min <= right_min else right_min
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
|
@ -1,58 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
# Divide and Conquer algorithm
|
||||
def find_min(nums: list[int | float], left: int, right: int) -> int | float:
|
||||
"""
|
||||
find min value in list
|
||||
:param nums: contains elements
|
||||
:param left: index of first element
|
||||
:param right: index of last element
|
||||
:return: min in nums
|
||||
|
||||
>>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]):
|
||||
... find_min(nums, 0, len(nums) - 1) == min(nums)
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
>>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]
|
||||
>>> find_min(nums, 0, len(nums) - 1) == min(nums)
|
||||
True
|
||||
>>> find_min([], 0, 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: find_min() arg is an empty sequence
|
||||
>>> find_min(nums, 0, len(nums)) == min(nums)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
>>> find_min(nums, -len(nums), -1) == min(nums)
|
||||
True
|
||||
>>> find_min(nums, -len(nums) - 1, -1) == min(nums)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
"""
|
||||
if len(nums) == 0:
|
||||
raise ValueError("find_min() arg is an empty sequence")
|
||||
if (
|
||||
left >= len(nums)
|
||||
or left < -len(nums)
|
||||
or right >= len(nums)
|
||||
or right < -len(nums)
|
||||
):
|
||||
raise IndexError("list index out of range")
|
||||
if left == right:
|
||||
return nums[left]
|
||||
mid = (left + right) >> 1 # the middle
|
||||
left_min = find_min(nums, left, mid) # find min in range[left, mid]
|
||||
right_min = find_min(nums, mid + 1, right) # find min in range[mid + 1, right]
|
||||
|
||||
return left_min if left_min <= right_min else right_min
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod(verbose=True)
|
@ -13,7 +13,7 @@ This script is inspired by a corresponding research paper.
|
||||
import numpy as np
|
||||
|
||||
|
||||
def sigmoid(vector: np.array) -> np.array:
|
||||
def sigmoid(vector: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Mathematical function sigmoid takes a vector x of K real numbers as input and
|
||||
returns 1/ (1 + e^-x).
|
||||
@ -25,17 +25,15 @@ def sigmoid(vector: np.array) -> np.array:
|
||||
return 1 / (1 + np.exp(-vector))
|
||||
|
||||
|
||||
def gaussian_error_linear_unit(vector: np.array) -> np.array:
|
||||
def gaussian_error_linear_unit(vector: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Implements the Gaussian Error Linear Unit (GELU) function
|
||||
|
||||
Parameters:
|
||||
vector (np.array): A numpy array of shape (1,n)
|
||||
consisting of real values
|
||||
vector (np.ndarray): A numpy array of shape (1, n) consisting of real values
|
||||
|
||||
Returns:
|
||||
gelu_vec (np.array): The input numpy array, after applying
|
||||
gelu.
|
||||
gelu_vec (np.ndarray): The input numpy array, after applying gelu
|
||||
|
||||
Examples:
|
||||
>>> gaussian_error_linear_unit(np.array([-1.0, 1.0, 2.0]))
|
||||
|
@ -14,7 +14,11 @@ Jaccard similarity is widely used with MinHashing.
|
||||
"""
|
||||
|
||||
|
||||
def jaccard_similarity(set_a, set_b, alternative_union=False):
|
||||
def jaccard_similarity(
|
||||
set_a: set[str] | list[str] | tuple[str],
|
||||
set_b: set[str] | list[str] | tuple[str],
|
||||
alternative_union=False,
|
||||
):
|
||||
"""
|
||||
Finds the jaccard similarity between two sets.
|
||||
Essentially, its intersection over union.
|
||||
@ -37,41 +41,52 @@ def jaccard_similarity(set_a, set_b, alternative_union=False):
|
||||
>>> set_b = {'c', 'd', 'e', 'f', 'h', 'i'}
|
||||
>>> jaccard_similarity(set_a, set_b)
|
||||
0.375
|
||||
|
||||
>>> jaccard_similarity(set_a, set_a)
|
||||
1.0
|
||||
|
||||
>>> jaccard_similarity(set_a, set_a, True)
|
||||
0.5
|
||||
|
||||
>>> set_a = ['a', 'b', 'c', 'd', 'e']
|
||||
>>> set_b = ('c', 'd', 'e', 'f', 'h', 'i')
|
||||
>>> jaccard_similarity(set_a, set_b)
|
||||
0.375
|
||||
>>> set_a = ('c', 'd', 'e', 'f', 'h', 'i')
|
||||
>>> set_b = ['a', 'b', 'c', 'd', 'e']
|
||||
>>> jaccard_similarity(set_a, set_b)
|
||||
0.375
|
||||
>>> set_a = ('c', 'd', 'e', 'f', 'h', 'i')
|
||||
>>> set_b = ['a', 'b', 'c', 'd']
|
||||
>>> jaccard_similarity(set_a, set_b, True)
|
||||
0.2
|
||||
>>> set_a = {'a', 'b'}
|
||||
>>> set_b = ['c', 'd']
|
||||
>>> jaccard_similarity(set_a, set_b)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Set a and b must either both be sets or be either a list or a tuple.
|
||||
"""
|
||||
|
||||
if isinstance(set_a, set) and isinstance(set_b, set):
|
||||
intersection = len(set_a.intersection(set_b))
|
||||
intersection_length = len(set_a.intersection(set_b))
|
||||
|
||||
if alternative_union:
|
||||
union = len(set_a) + len(set_b)
|
||||
union_length = len(set_a) + len(set_b)
|
||||
else:
|
||||
union = len(set_a.union(set_b))
|
||||
union_length = len(set_a.union(set_b))
|
||||
|
||||
return intersection / union
|
||||
return intersection_length / union_length
|
||||
|
||||
if isinstance(set_a, (list, tuple)) and isinstance(set_b, (list, tuple)):
|
||||
elif isinstance(set_a, (list, tuple)) and isinstance(set_b, (list, tuple)):
|
||||
intersection = [element for element in set_a if element in set_b]
|
||||
|
||||
if alternative_union:
|
||||
union = len(set_a) + len(set_b)
|
||||
return len(intersection) / union
|
||||
return len(intersection) / (len(set_a) + len(set_b))
|
||||
else:
|
||||
union = set_a + [element for element in set_b if element not in set_a]
|
||||
# Cast set_a to list because tuples cannot be mutated
|
||||
union = list(set_a) + [element for element in set_b if element not in set_a]
|
||||
return len(intersection) / len(union)
|
||||
|
||||
return len(intersection) / len(union)
|
||||
return None
|
||||
raise ValueError(
|
||||
"Set a and b must either both be sets or be either a list or a tuple."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -1,16 +1,20 @@
|
||||
"""
|
||||
Author: P Shreyas Shetty
|
||||
Implementation of Newton-Raphson method for solving equations of kind
|
||||
f(x) = 0. It is an iterative method where solution is found by the expression
|
||||
x[n+1] = x[n] + f(x[n])/f'(x[n])
|
||||
If no solution exists, then either the solution will not be found when iteration
|
||||
limit is reached or the gradient f'(x[n]) approaches zero. In both cases, exception
|
||||
is raised. If iteration limit is reached, try increasing maxiter.
|
||||
"""
|
||||
Author: P Shreyas Shetty
|
||||
Implementation of Newton-Raphson method for solving equations of kind
|
||||
f(x) = 0. It is an iterative method where solution is found by the expression
|
||||
x[n+1] = x[n] + f(x[n])/f'(x[n])
|
||||
If no solution exists, then either the solution will not be found when iteration
|
||||
limit is reached or the gradient f'(x[n]) approaches zero. In both cases, exception
|
||||
is raised. If iteration limit is reached, try increasing maxiter.
|
||||
"""
|
||||
|
||||
import math as m
|
||||
from collections.abc import Callable
|
||||
|
||||
DerivativeFunc = Callable[[float], float]
|
||||
|
||||
|
||||
def calc_derivative(f, a, h=0.001):
|
||||
def calc_derivative(f: DerivativeFunc, a: float, h: float = 0.001) -> float:
|
||||
"""
|
||||
Calculates derivative at point a for function f using finite difference
|
||||
method
|
||||
@ -18,7 +22,14 @@ def calc_derivative(f, a, h=0.001):
|
||||
return (f(a + h) - f(a - h)) / (2 * h)
|
||||
|
||||
|
||||
def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6, logsteps=False):
|
||||
def newton_raphson(
|
||||
f: DerivativeFunc,
|
||||
x0: float = 0,
|
||||
maxiter: int = 100,
|
||||
step: float = 0.0001,
|
||||
maxerror: float = 1e-6,
|
||||
logsteps: bool = False,
|
||||
) -> tuple[float, float, list[float]]:
|
||||
a = x0 # set the initial guess
|
||||
steps = [a]
|
||||
error = abs(f(a))
|
||||
@ -36,7 +47,7 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6, logsteps=Fa
|
||||
if logsteps:
|
||||
# If logstep is true, then log intermediate steps
|
||||
return a, error, steps
|
||||
return a, error
|
||||
return a, error, []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -1,7 +1,7 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
def qr_householder(a):
|
||||
def qr_householder(a: np.ndarray):
|
||||
"""Return a QR-decomposition of the matrix A using Householder reflection.
|
||||
|
||||
The QR-decomposition decomposes the matrix A of shape (m, n) into an
|
||||
|
@ -11,7 +11,7 @@ https://en.wikipedia.org/wiki/Sigmoid_function
|
||||
import numpy as np
|
||||
|
||||
|
||||
def sigmoid(vector: np.array) -> np.array:
|
||||
def sigmoid(vector: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Implements the sigmoid function
|
||||
|
||||
|
@ -12,12 +12,12 @@ https://en.wikipedia.org/wiki/Activation_function
|
||||
import numpy as np
|
||||
|
||||
|
||||
def tangent_hyperbolic(vector: np.array) -> np.array:
|
||||
def tangent_hyperbolic(vector: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Implements the tanh function
|
||||
|
||||
Parameters:
|
||||
vector: np.array
|
||||
vector: np.ndarray
|
||||
|
||||
Returns:
|
||||
tanh (np.array): The input numpy array after applying tanh.
|
||||
|
@ -0,0 +1,39 @@
|
||||
"""
|
||||
Leaky Rectified Linear Unit (Leaky ReLU)
|
||||
|
||||
Use Case: Leaky ReLU addresses the problem of the vanishing gradient.
|
||||
For more detailed information, you can refer to the following link:
|
||||
https://en.wikipedia.org/wiki/Rectifier_(neural_networks)#Leaky_ReLU
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def leaky_rectified_linear_unit(vector: np.ndarray, alpha: float) -> np.ndarray:
|
||||
"""
|
||||
Implements the LeakyReLU activation function.
|
||||
|
||||
Parameters:
|
||||
vector (np.ndarray): The input array for LeakyReLU activation.
|
||||
alpha (float): The slope for negative values.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The input array after applying the LeakyReLU activation.
|
||||
|
||||
Formula: f(x) = x if x > 0 else f(x) = alpha * x
|
||||
|
||||
Examples:
|
||||
>>> leaky_rectified_linear_unit(vector=np.array([2.3,0.6,-2,-3.8]), alpha=0.3)
|
||||
array([ 2.3 , 0.6 , -0.6 , -1.14])
|
||||
|
||||
>>> leaky_rectified_linear_unit(np.array([-9.2, -0.3, 0.45, -4.56]), alpha=0.067)
|
||||
array([-0.6164 , -0.0201 , 0.45 , -0.30552])
|
||||
|
||||
"""
|
||||
return np.where(vector > 0, vector, alpha * vector)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
@ -53,6 +53,40 @@ def volume_of_gas_system(moles: float, kelvin: float, pressure: float) -> float:
|
||||
return moles * kelvin * UNIVERSAL_GAS_CONSTANT / pressure
|
||||
|
||||
|
||||
def temperature_of_gas_system(moles: float, volume: float, pressure: float) -> float:
|
||||
"""
|
||||
>>> temperature_of_gas_system(2, 100, 5)
|
||||
30.068090996146232
|
||||
>>> temperature_of_gas_system(11, 5009, 1000)
|
||||
54767.66101807144
|
||||
>>> temperature_of_gas_system(3, -0.46, 23.5)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid inputs. Enter positive value.
|
||||
"""
|
||||
if moles < 0 or volume < 0 or pressure < 0:
|
||||
raise ValueError("Invalid inputs. Enter positive value.")
|
||||
|
||||
return pressure * volume / (moles * UNIVERSAL_GAS_CONSTANT)
|
||||
|
||||
|
||||
def moles_of_gas_system(kelvin: float, volume: float, pressure: float) -> float:
|
||||
"""
|
||||
>>> moles_of_gas_system(100, 5, 10)
|
||||
0.06013618199229246
|
||||
>>> moles_of_gas_system(110, 5009, 1000)
|
||||
5476.766101807144
|
||||
>>> moles_of_gas_system(3, -0.46, 23.5)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid inputs. Enter positive value.
|
||||
"""
|
||||
if kelvin < 0 or volume < 0 or pressure < 0:
|
||||
raise ValueError("Invalid inputs. Enter positive value.")
|
||||
|
||||
return pressure * volume / (kelvin * UNIVERSAL_GAS_CONSTANT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
|
@ -226,7 +226,7 @@ def plot(
|
||||
No doctest provided since this function does not have a return value.
|
||||
"""
|
||||
fig = plt.figure()
|
||||
fig.canvas.set_window_title(title)
|
||||
fig.canvas.manager.set_window_title(title)
|
||||
ax = plt.axes(
|
||||
xlim=(x_start, x_end), ylim=(y_start, y_end)
|
||||
) # Set section to be plotted
|
||||
|
117
strings/is_valid_email_address.py
Normal file
117
strings/is_valid_email_address.py
Normal file
@ -0,0 +1,117 @@
|
||||
"""
|
||||
Implements an is valid email address algorithm
|
||||
|
||||
@ https://en.wikipedia.org/wiki/Email_address
|
||||
"""
|
||||
|
||||
import string
|
||||
|
||||
email_tests: tuple[tuple[str, bool], ...] = (
|
||||
("simple@example.com", True),
|
||||
("very.common@example.com", True),
|
||||
("disposable.style.email.with+symbol@example.com", True),
|
||||
("other-email-with-hyphen@and.subdomains.example.com", True),
|
||||
("fully-qualified-domain@example.com", True),
|
||||
("user.name+tag+sorting@example.com", True),
|
||||
("x@example.com", True),
|
||||
("example-indeed@strange-example.com", True),
|
||||
("test/test@test.com", True),
|
||||
(
|
||||
"123456789012345678901234567890123456789012345678901234567890123@example.com",
|
||||
True,
|
||||
),
|
||||
("admin@mailserver1", True),
|
||||
("example@s.example", True),
|
||||
("Abc.example.com", False),
|
||||
("A@b@c@example.com", False),
|
||||
("abc@example..com", False),
|
||||
("a(c)d,e:f;g<h>i[j\\k]l@example.com", False),
|
||||
(
|
||||
"12345678901234567890123456789012345678901234567890123456789012345@example.com",
|
||||
False,
|
||||
),
|
||||
("i.like.underscores@but_its_not_allowed_in_this_part", False),
|
||||
("", False),
|
||||
)
|
||||
|
||||
# The maximum octets (one character as a standard unicode character is one byte)
|
||||
# that the local part and the domain part can have
|
||||
MAX_LOCAL_PART_OCTETS = 64
|
||||
MAX_DOMAIN_OCTETS = 255
|
||||
|
||||
|
||||
def is_valid_email_address(email: str) -> bool:
|
||||
"""
|
||||
Returns True if the passed email address is valid.
|
||||
|
||||
The local part of the email precedes the singular @ symbol and
|
||||
is associated with a display-name. For example, "john.smith"
|
||||
The domain is stricter than the local part and follows the @ symbol.
|
||||
|
||||
Global email checks:
|
||||
1. There can only be one @ symbol in the email address. Technically if the
|
||||
@ symbol is quoted in the local-part, then it is valid, however this
|
||||
implementation ignores "" for now.
|
||||
(See https://en.wikipedia.org/wiki/Email_address#:~:text=If%20quoted,)
|
||||
2. The local-part and the domain are limited to a certain number of octets. With
|
||||
unicode storing a single character in one byte, each octet is equivalent to
|
||||
a character. Hence, we can just check the length of the string.
|
||||
Checks for the local-part:
|
||||
3. The local-part may contain: upper and lowercase latin letters, digits 0 to 9,
|
||||
and printable characters (!#$%&'*+-/=?^_`{|}~)
|
||||
4. The local-part may also contain a "." in any place that is not the first or
|
||||
last character, and may not have more than one "." consecutively.
|
||||
|
||||
Checks for the domain:
|
||||
5. The domain may contain: upper and lowercase latin letters and digits 0 to 9
|
||||
6. Hyphen "-", provided that it is not the first or last character
|
||||
7. The domain may also contain a "." in any place that is not the first or
|
||||
last character, and may not have more than one "." consecutively.
|
||||
|
||||
>>> for email, valid in email_tests:
|
||||
... assert is_valid_email_address(email) == valid
|
||||
"""
|
||||
|
||||
# (1.) Make sure that there is only one @ symbol in the email address
|
||||
if email.count("@") != 1:
|
||||
return False
|
||||
|
||||
local_part, domain = email.split("@")
|
||||
# (2.) Check octet length of the local part and domain
|
||||
if len(local_part) > MAX_LOCAL_PART_OCTETS or len(domain) > MAX_DOMAIN_OCTETS:
|
||||
return False
|
||||
|
||||
# (3.) Validate the characters in the local-part
|
||||
if any(
|
||||
char not in string.ascii_letters + string.digits + ".(!#$%&'*+-/=?^_`{|}~)"
|
||||
for char in local_part
|
||||
):
|
||||
return False
|
||||
|
||||
# (4.) Validate the placement of "." characters in the local-part
|
||||
if local_part.startswith(".") or local_part.endswith(".") or ".." in local_part:
|
||||
return False
|
||||
|
||||
# (5.) Validate the characters in the domain
|
||||
if any(char not in string.ascii_letters + string.digits + ".-" for char in domain):
|
||||
return False
|
||||
|
||||
# (6.) Validate the placement of "-" characters
|
||||
if domain.startswith("-") or domain.endswith("."):
|
||||
return False
|
||||
|
||||
# (7.) Validate the placement of "." characters
|
||||
if domain.startswith(".") or domain.endswith(".") or ".." in domain:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
for email, valid in email_tests:
|
||||
is_valid = is_valid_email_address(email)
|
||||
assert is_valid == valid, f"{email} is {is_valid}"
|
||||
print(f"Email address {email} is {'not ' if not is_valid else ''}valid")
|
Loading…
x
Reference in New Issue
Block a user