mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-02-25 18:38:39 +00:00
Compare commits
6 Commits
4f2a346c27
...
ac68dc1128
Author | SHA1 | Date | |
---|---|---|---|
|
ac68dc1128 | ||
|
4b7ecb6a81 | ||
|
c290dd6a43 | ||
|
02d89bde67 | ||
|
f24ab2c60d | ||
|
9d86d4edaa |
@ -74,6 +74,7 @@
|
|||||||
* [Game Of Life](cellular_automata/game_of_life.py)
|
* [Game Of Life](cellular_automata/game_of_life.py)
|
||||||
* [Nagel Schrekenberg](cellular_automata/nagel_schrekenberg.py)
|
* [Nagel Schrekenberg](cellular_automata/nagel_schrekenberg.py)
|
||||||
* [One Dimensional](cellular_automata/one_dimensional.py)
|
* [One Dimensional](cellular_automata/one_dimensional.py)
|
||||||
|
* [Wa Tor](cellular_automata/wa_tor.py)
|
||||||
|
|
||||||
## Ciphers
|
## Ciphers
|
||||||
* [A1Z26](ciphers/a1z26.py)
|
* [A1Z26](ciphers/a1z26.py)
|
||||||
@ -335,9 +336,11 @@
|
|||||||
* [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py)
|
* [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py)
|
||||||
* [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py)
|
* [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py)
|
||||||
* [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py)
|
* [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py)
|
||||||
|
* [Regex Match](dynamic_programming/regex_match.py)
|
||||||
* [Rod Cutting](dynamic_programming/rod_cutting.py)
|
* [Rod Cutting](dynamic_programming/rod_cutting.py)
|
||||||
* [Subset Generation](dynamic_programming/subset_generation.py)
|
* [Subset Generation](dynamic_programming/subset_generation.py)
|
||||||
* [Sum Of Subset](dynamic_programming/sum_of_subset.py)
|
* [Sum Of Subset](dynamic_programming/sum_of_subset.py)
|
||||||
|
* [Tribonacci](dynamic_programming/tribonacci.py)
|
||||||
* [Viterbi](dynamic_programming/viterbi.py)
|
* [Viterbi](dynamic_programming/viterbi.py)
|
||||||
* [Word Break](dynamic_programming/word_break.py)
|
* [Word Break](dynamic_programming/word_break.py)
|
||||||
|
|
||||||
@ -1169,6 +1172,7 @@
|
|||||||
* [Is Pangram](strings/is_pangram.py)
|
* [Is Pangram](strings/is_pangram.py)
|
||||||
* [Is Spain National Id](strings/is_spain_national_id.py)
|
* [Is Spain National Id](strings/is_spain_national_id.py)
|
||||||
* [Is Srilankan Phone Number](strings/is_srilankan_phone_number.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)
|
* [Jaro Winkler](strings/jaro_winkler.py)
|
||||||
* [Join](strings/join.py)
|
* [Join](strings/join.py)
|
||||||
* [Knuth Morris Pratt](strings/knuth_morris_pratt.py)
|
* [Knuth Morris Pratt](strings/knuth_morris_pratt.py)
|
||||||
|
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)
|
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()
|
@ -1,4 +1,4 @@
|
|||||||
total_user,total_events,days
|
total_users,total_events,days
|
||||||
18231,0.0,1
|
18231,0.0,1
|
||||||
22621,1.0,2
|
22621,1.0,2
|
||||||
15675,0.0,3
|
15675,0.0,3
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
this is code for forecasting
|
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
|
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)
|
missing (the amount of data that u expected are not supposed to be)
|
||||||
then we can use it
|
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
|
u can just adjust it for ur own purpose
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from warnings import simplefilter
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from sklearn.preprocessing import Normalizer
|
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])
|
>>> sarimax_predictor([4,2,6,8], [3,1,2,4], [2])
|
||||||
6.6666671111109626
|
6.6666671111109626
|
||||||
"""
|
"""
|
||||||
|
# Suppress the User Warning raised by SARIMAX due to insufficient observations
|
||||||
|
simplefilter("ignore", UserWarning)
|
||||||
order = (1, 2, 1)
|
order = (1, 2, 1)
|
||||||
seasonal_order = (1, 1, 0, 7)
|
seasonal_order = (1, 1, 1, 7)
|
||||||
model = SARIMAX(
|
model = SARIMAX(
|
||||||
train_user, exog=train_match, order=order, seasonal_order=seasonal_order
|
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
|
safe = 0
|
||||||
not_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:
|
for i in list_vote:
|
||||||
if i > actual_result:
|
if i > actual_result:
|
||||||
safe = not_safe + 1
|
safe = not_safe + 1
|
||||||
@ -114,16 +122,11 @@ def data_safety_checker(list_vote: list, actual_result: float) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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,
|
data column = total user in a day, how much online event held in one day,
|
||||||
what day is that(sunday-saturday)
|
what day is that(sunday-saturday)
|
||||||
"""
|
"""
|
||||||
|
data_input_df = pd.read_csv("ex_data.csv")
|
||||||
|
|
||||||
# start normalization
|
# start normalization
|
||||||
normalize_df = Normalizer().fit_transform(data_input_df.values)
|
normalize_df = Normalizer().fit_transform(data_input_df.values)
|
||||||
@ -138,23 +141,23 @@ if __name__ == "__main__":
|
|||||||
x_test = x[len(x) - 1 :]
|
x_test = x[len(x) - 1 :]
|
||||||
|
|
||||||
# for linear regression & sarimax
|
# for linear regression & sarimax
|
||||||
trn_date = total_date[: len(total_date) - 1]
|
train_date = total_date[: len(total_date) - 1]
|
||||||
trn_user = total_user[: len(total_user) - 1]
|
train_user = total_user[: len(total_user) - 1]
|
||||||
trn_match = total_match[: len(total_match) - 1]
|
train_match = total_match[: len(total_match) - 1]
|
||||||
|
|
||||||
tst_date = total_date[len(total_date) - 1 :]
|
test_date = total_date[len(total_date) - 1 :]
|
||||||
tst_user = total_user[len(total_user) - 1 :]
|
test_user = total_user[len(total_user) - 1 :]
|
||||||
tst_match = total_match[len(total_match) - 1 :]
|
test_match = total_match[len(total_match) - 1 :]
|
||||||
|
|
||||||
# voting system with forecasting
|
# voting system with forecasting
|
||||||
res_vote = [
|
res_vote = [
|
||||||
linear_regression_prediction(
|
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),
|
sarimax_predictor(train_user, train_match, test_match),
|
||||||
support_vector_regressor(x_train, x_test, trn_user),
|
support_vector_regressor(x_train, x_test, train_user),
|
||||||
]
|
]
|
||||||
|
|
||||||
# check the safety of today's data
|
# check the safety of today's data
|
||||||
not_str = "" if data_safety_checker(res_vote, tst_user) else "not "
|
not_str = "" if data_safety_checker(res_vote, test_user[0]) else "not "
|
||||||
print("Today's data is {not_str}safe.")
|
print(f"Today's data is {not_str}safe.")
|
||||||
|
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