From c0867d8145c718c596dbb2acf17478d9dfb32d74 Mon Sep 17 00:00:00 2001 From: Sergei Bezymyanniy Date: Tue, 22 Sep 2026 13:17:34 +0300 Subject: [PATCH 1/4] Solve Task 1 --- Solve Task 1/constants.py | 11 +++++++++++ Solve Task 1/snowflake.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 Solve Task 1/constants.py create mode 100644 Solve Task 1/snowflake.py diff --git a/Solve Task 1/constants.py b/Solve Task 1/constants.py new file mode 100644 index 00000000..f998c23b --- /dev/null +++ b/Solve Task 1/constants.py @@ -0,0 +1,11 @@ +from time import time + +CURRENT_EPOCH_MS = int(time() * 1000) +EPOCH_MS_DEFAULT = 1288834974657 +NODE_ID_DEFAULT = 1 +TIMESTAMP_BITS = 41 +NODE_ID_BITS = 10 +SEQUENCE_ID_BITS = 12 +TIMESTAMP_MS_MAX = 2**TIMESTAMP_BITS - 1 +NODE_ID_MAX = 2**NODE_ID_BITS - 1 +SEQUENCE_ID_MAX = 2**SEQUENCE_ID_BITS - 1 \ No newline at end of file diff --git a/Solve Task 1/snowflake.py b/Solve Task 1/snowflake.py new file mode 100644 index 00000000..264f77b2 --- /dev/null +++ b/Solve Task 1/snowflake.py @@ -0,0 +1,36 @@ +from constants import * + +def read_current_millis (epoch_ms : int) -> int: + return CURRENT_EPOCH_MS - epoch_ms + + +def generate_snowflake_id(sequence_id: int, node_id: int = NODE_ID_DEFAULT, epoch_ms: int = EPOCH_MS_DEFAULT) -> int | None: + if not (0<= node_id <= NODE_ID_MAX): + print (f"node_id must be in [0 ; {NODE_ID_MAX}]") + return None + + if not (0<= sequence_id <= SEQUENCE_ID_MAX): + print (f"sequence_id must be in [0 ; {SEQUENCE_ID_MAX}]") + return None + + timestamp_ms = read_current_millis(epoch_ms) + + if timestamp_ms > TIMESTAMP_MS_MAX: + print ("Timestamp overflows") + return None + + snowflake_id = (timestamp_ms << (NODE_ID_BITS + SEQUENCE_ID_BITS)) | (node_id << SEQUENCE_ID_BITS) | sequence_id + return snowflake_id + + +def decode_sequence_id(snowflake_id: int) -> int: + return snowflake_id & SEQUENCE_ID_MAX + + +def decode_node_id(snowflake_id: int) -> int: + return (snowflake_id >> SEQUENCE_ID_BITS) & NODE_ID_MAX + + +def decode_timestamp_ms(snowflake_id: int, epoch_ms: int = EPOCH_MS_DEFAULT) -> int: + return ((snowflake_id >> NODE_ID_BITS + SEQUENCE_ID_BITS) & TIMESTAMP_MS_MAX) + epoch_ms + From 05347ac2ed2f9c5cc7cd62fc660e4bf064da8f4d Mon Sep 17 00:00:00 2001 From: Sergei Bezymyanniy Date: Fri, 2 Oct 2026 16:58:23 +0300 Subject: [PATCH 2/4] Solve Task 2 --- Solve Task 2/cart.py | 99 +++++++++++++++++++++++++++++++++++++++++ Solve Task 2/crod.py | 65 +++++++++++++++++++++++++++ Solve Task 2/storage.py | 11 +++++ Solve Task 2/utils.py | 9 ++++ 4 files changed, 184 insertions(+) create mode 100644 Solve Task 2/cart.py create mode 100644 Solve Task 2/crod.py create mode 100644 Solve Task 2/storage.py create mode 100644 Solve Task 2/utils.py diff --git a/Solve Task 2/cart.py b/Solve Task 2/cart.py new file mode 100644 index 00000000..1840ad0b --- /dev/null +++ b/Solve Task 2/cart.py @@ -0,0 +1,99 @@ +from typing import Final +from crod import ( + read_product, + update_product, +) +from storage import ( + QUANTITY_INDEX, + NAME_INDEX, + PRICE_INDEX, + Product) + +CartLine = tuple[int, int] +LINE_PRODUCT_ID_INDEX: Final = 0 +LINE_QUANTITY_INDEX: Final = 1 + + +def find_cart_line(cart: list[CartLine], + product_id: int,) -> CartLine | None: + for line in cart: + if line[LINE_PRODUCT_ID_INDEX] == product_id: + return line + print(f"Product {product_id} is not in the cart/") + return None + + +def add_to_cart(storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int) -> CartLine | None: + product = read_product(storage, product_id) + if product is None: + return product + storage_quantity = product[QUANTITY_INDEX] + if storage_quantity < quantity: + print(f"Not enough stock for product {product_id}: " + f"{storage_quantity} available, " + f"{quantity} requested.") + return None + + new_field = ( + product[NAME_INDEX], + product[PRICE_INDEX], + storage_quantity - quantity + ) + update_product(storage, product_id, new_field) + + for index, line in enumerate(cart): + if line[LINE_PRODUCT_ID_INDEX] == product_id: + new_line: CartLine = ( + product_id, + line[LINE_QUANTITY_INDEX] + quantity + ) + cart[index] = new_line + return new_line + new_line: CartLine = (product_id, quantity) + cart.append(new_line) + return new_line + + +def remove_from_cart(storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int) -> CartLine | None: + cart_line = find_cart_line(cart, product_id) + product = read_product(storage, product_id) + if cart_line is None: + return cart_line + if cart_line[LINE_QUANTITY_INDEX] < quantity: + print(f"Cart holds only {cart_line[LINE_QUANTITY_INDEX]} " + f"unit(s) of product {product_id}, cannot remove {quantity}.") + return None + if product is None: + return None + new_quantity = product[QUANTITY_INDEX] + quantity + new_fields = ( + product[NAME_INDEX], + product[PRICE_INDEX], + new_quantity, + ) + update_product( + storage, + product_id, + new_fields, + ) + new_line: CartLine = ( + product_id, + cart_line[LINE_QUANTITY_INDEX] - quantity, + ) + new_line: CartLine = ( + product_id, + cart_line[LINE_QUANTITY_INDEX]-quantity + ) + if new_line[LINE_QUANTITY_INDEX] == 0: + cart.remove(cart_line) + return new_line + for index, line in enumerate(cart): + if line == cart_line: + cart[index] = new_line + return new_line diff --git a/Solve Task 2/crod.py b/Solve Task 2/crod.py new file mode 100644 index 00000000..6202f484 --- /dev/null +++ b/Solve Task 2/crod.py @@ -0,0 +1,65 @@ +from decimal import Decimal +from storage import ( + PRODUCT_ID_MIN, + PRODUCT_ID_INDEX, + NAME_INDEX, + Product, +) +from utils import normalize_price + + +def create_product(storage: list[Product], + fields: tuple[str, Decimal, int]) -> int | None: + product_name, product_price, product_quantity = fields + for product in storage: + if product_name == product[NAME_INDEX]: + print(f"Product name {product_name} is already taken.") + return None + product_id = generate_product_id(storage) + storage.append(( + product_id, + product_name, + normalize_price(product_price), + product_quantity + )) + return product_id + + +def generate_product_id(storage: list[Product]) -> int: + if not storage: + return PRODUCT_ID_MIN + MAX_INDEX = max(product[PRODUCT_ID_INDEX] for product in storage) + return MAX_INDEX + 1 + + +def read_product(storage: list[Product], product_id: int) -> Product | None: + for product in storage: + if product[PRODUCT_ID_INDEX] == product_id: + return product + print(f"No product with id {product_id}") + return None + + +def update_product( + storage: list[Product], + product_id: int, + fields: tuple[str, Decimal, int], +) -> Product | None: + for index, product in enumerate(storage): + if product[PRODUCT_ID_INDEX] == product_id: + new_name, new_price, new_quantity = fields + norm_price = normalize_price(new_price) + NEW_PRODUCT = (product_id, new_name, norm_price, new_quantity) + storage[index] = NEW_PRODUCT + return NEW_PRODUCT + print(f"No product with id {product_id}") + return None + + +def delete_product(storage: list[Product], product_id: int) -> int | None: + for index, product in enumerate(storage): + if product[PRODUCT_ID_INDEX] == product_id: + storage.pop(index) + return product_id + print(f"No product with id {product_id}.") + return None diff --git a/Solve Task 2/storage.py b/Solve Task 2/storage.py new file mode 100644 index 00000000..cdd56923 --- /dev/null +++ b/Solve Task 2/storage.py @@ -0,0 +1,11 @@ +from decimal import Decimal +from typing import Final + +Product = tuple[int, str, Decimal, int] + +PRODUCT_ID_INDEX: Final[int] = 0 +NAME_INDEX: Final[int] = 1 +PRICE_INDEX: Final[int] = 2 +QUANTITY_INDEX: Final[int] = 3 + +PRODUCT_ID_MIN: Final[int] = 1 diff --git a/Solve Task 2/utils.py b/Solve Task 2/utils.py new file mode 100644 index 00000000..7d9077bb --- /dev/null +++ b/Solve Task 2/utils.py @@ -0,0 +1,9 @@ +from typing import Final +from decimal import Decimal, ROUND_HALF_UP + +PRICE_PRECISION: Final[int] = 2 +PRICE_STEP: Final = Decimal(1).scaleb(-PRICE_PRECISION) + + +def normalize_price(price: Decimal) -> Decimal: + return price.quantize(PRICE_STEP, rounding=ROUND_HALF_UP) From d44afc1792e7541f35e6362d08fdce467fa15390 Mon Sep 17 00:00:00 2001 From: Sergei Bezymyanniy Date: Fri, 2 Oct 2026 19:32:51 +0300 Subject: [PATCH 3/4] Solve Task 2 --- Solve Task 2/cart.py | 2 +- Solve Task 2/{crod.py => crud.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Solve Task 2/{crod.py => crud.py} (100%) diff --git a/Solve Task 2/cart.py b/Solve Task 2/cart.py index 1840ad0b..61036def 100644 --- a/Solve Task 2/cart.py +++ b/Solve Task 2/cart.py @@ -1,5 +1,5 @@ from typing import Final -from crod import ( +from crud import ( read_product, update_product, ) diff --git a/Solve Task 2/crod.py b/Solve Task 2/crud.py similarity index 100% rename from Solve Task 2/crod.py rename to Solve Task 2/crud.py From e94301f9255224e564bb608a36704d41e489fe12 Mon Sep 17 00:00:00 2001 From: Sergei Bezymyanniy Date: Sun, 4 Oct 2026 16:42:26 +0300 Subject: [PATCH 4/4] Solve task 2 --- Solve Task 1/constants.py | 11 ---- Solve Task 1/snowflake.py | 36 ----------- src/part2/cart.py | 129 ++++++++++++++++++++++++++++++++++++++ src/part2/crud.py | 91 +++++++++++++++++++++++++++ src/part2/storage.py | 12 ++++ src/part2/utils.py | 10 +++ 6 files changed, 242 insertions(+), 47 deletions(-) delete mode 100644 Solve Task 1/constants.py delete mode 100644 Solve Task 1/snowflake.py create mode 100644 src/part2/cart.py create mode 100644 src/part2/crud.py create mode 100644 src/part2/storage.py create mode 100644 src/part2/utils.py diff --git a/Solve Task 1/constants.py b/Solve Task 1/constants.py deleted file mode 100644 index f998c23b..00000000 --- a/Solve Task 1/constants.py +++ /dev/null @@ -1,11 +0,0 @@ -from time import time - -CURRENT_EPOCH_MS = int(time() * 1000) -EPOCH_MS_DEFAULT = 1288834974657 -NODE_ID_DEFAULT = 1 -TIMESTAMP_BITS = 41 -NODE_ID_BITS = 10 -SEQUENCE_ID_BITS = 12 -TIMESTAMP_MS_MAX = 2**TIMESTAMP_BITS - 1 -NODE_ID_MAX = 2**NODE_ID_BITS - 1 -SEQUENCE_ID_MAX = 2**SEQUENCE_ID_BITS - 1 \ No newline at end of file diff --git a/Solve Task 1/snowflake.py b/Solve Task 1/snowflake.py deleted file mode 100644 index 264f77b2..00000000 --- a/Solve Task 1/snowflake.py +++ /dev/null @@ -1,36 +0,0 @@ -from constants import * - -def read_current_millis (epoch_ms : int) -> int: - return CURRENT_EPOCH_MS - epoch_ms - - -def generate_snowflake_id(sequence_id: int, node_id: int = NODE_ID_DEFAULT, epoch_ms: int = EPOCH_MS_DEFAULT) -> int | None: - if not (0<= node_id <= NODE_ID_MAX): - print (f"node_id must be in [0 ; {NODE_ID_MAX}]") - return None - - if not (0<= sequence_id <= SEQUENCE_ID_MAX): - print (f"sequence_id must be in [0 ; {SEQUENCE_ID_MAX}]") - return None - - timestamp_ms = read_current_millis(epoch_ms) - - if timestamp_ms > TIMESTAMP_MS_MAX: - print ("Timestamp overflows") - return None - - snowflake_id = (timestamp_ms << (NODE_ID_BITS + SEQUENCE_ID_BITS)) | (node_id << SEQUENCE_ID_BITS) | sequence_id - return snowflake_id - - -def decode_sequence_id(snowflake_id: int) -> int: - return snowflake_id & SEQUENCE_ID_MAX - - -def decode_node_id(snowflake_id: int) -> int: - return (snowflake_id >> SEQUENCE_ID_BITS) & NODE_ID_MAX - - -def decode_timestamp_ms(snowflake_id: int, epoch_ms: int = EPOCH_MS_DEFAULT) -> int: - return ((snowflake_id >> NODE_ID_BITS + SEQUENCE_ID_BITS) & TIMESTAMP_MS_MAX) + epoch_ms - diff --git a/src/part2/cart.py b/src/part2/cart.py new file mode 100644 index 00000000..cbdaa539 --- /dev/null +++ b/src/part2/cart.py @@ -0,0 +1,129 @@ +from typing import Final + +from .crud import read_product, update_product +from .storage import ( + NAME_INDEX, + PRICE_INDEX, + QUANTITY_INDEX, + Product, +) + + +CartLine = tuple[int, int] + +LINE_PRODUCT_ID_INDEX: Final[int] = 0 +LINE_QUANTITY_INDEX: Final[int] = 1 + + +def find_cart_line( + cart: list[CartLine], + product_id: int, +) -> CartLine | None: + for line in cart: + if line[LINE_PRODUCT_ID_INDEX] == product_id: + return line + + print(f"Product {product_id} is not in the cart") + return None + + +def add_to_cart( + storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int, +) -> CartLine | None: + product = read_product(storage, product_id) + + if product is None: + return None + + storage_quantity = product[QUANTITY_INDEX] + + if storage_quantity < quantity: + print( + f"Not enough stock for product {product_id}: " + f"{storage_quantity} available, " + f"{quantity} requested." + ) + return None + + new_fields = ( + product[NAME_INDEX], + product[PRICE_INDEX], + storage_quantity - quantity, + ) + + update_product( + storage, + product_id, + new_fields, + ) + + for index, line in enumerate(cart): + if line[LINE_PRODUCT_ID_INDEX] == product_id: + new_line = ( + product_id, + line[LINE_QUANTITY_INDEX] + quantity, + ) + cart[index] = new_line + return new_line + + new_line = (product_id, quantity) + cart.append(new_line) + return new_line + + +def remove_from_cart( + storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int, +) -> CartLine | None: + cart_line = find_cart_line(cart, product_id) + + if cart_line is None: + return None + + if cart_line[LINE_QUANTITY_INDEX] < quantity: + print( + f"Cart holds only {cart_line[LINE_QUANTITY_INDEX]} " + f"unit(s) of product {product_id}, " + f"cannot remove {quantity}." + ) + return None + + product = read_product(storage, product_id) + + if product is None: + return None + + new_quantity = product[QUANTITY_INDEX] + quantity + + new_fields = ( + product[NAME_INDEX], + product[PRICE_INDEX], + new_quantity, + ) + + update_product( + storage, + product_id, + new_fields, + ) + + new_line: CartLine = ( + product_id, + cart_line[LINE_QUANTITY_INDEX] - quantity, + ) + + if new_line[LINE_QUANTITY_INDEX] == 0: + cart.remove(cart_line) + return new_line + + for index, line in enumerate(cart): + if line == cart_line: + cart[index] = new_line + return new_line + + return None diff --git a/src/part2/crud.py b/src/part2/crud.py new file mode 100644 index 00000000..a37746cc --- /dev/null +++ b/src/part2/crud.py @@ -0,0 +1,91 @@ +from decimal import Decimal + +from .storage import ( + NAME_INDEX, + PRODUCT_ID_INDEX, + PRODUCT_ID_MIN, + Product, +) +from .utils import normalize_price + + +def create_product( + storage: list[Product], + fields: tuple[str, Decimal, int], +) -> int | None: + product_name, product_price, product_quantity = fields + + for product in storage: + if product_name == product[NAME_INDEX]: + print(f"Product name {product_name} is already taken.") + return None + + product_id = generate_product_id(storage) + + storage.append( + ( + product_id, + product_name, + normalize_price(product_price), + product_quantity, + ) + ) + + return product_id + + +def generate_product_id(storage: list[Product]) -> int: + if not storage: + return PRODUCT_ID_MIN + + max_id = max(product[PRODUCT_ID_INDEX] for product in storage) + + return max_id + 1 + + +def read_product( + storage: list[Product], + product_id: int, +) -> Product | None: + for product in storage: + if product[PRODUCT_ID_INDEX] == product_id: + return product + + print(f"No product with id {product_id}") + return None + + +def update_product( + storage: list[Product], + product_id: int, + fields: tuple[str, Decimal, int], +) -> Product | None: + for index, product in enumerate(storage): + if product[PRODUCT_ID_INDEX] == product_id: + new_name, new_price, new_quantity = fields + + new_product: Product = ( + product_id, + new_name, + normalize_price(new_price), + new_quantity, + ) + + storage[index] = new_product + return new_product + + print(f"No product with id {product_id}") + return None + + +def delete_product( + storage: list[Product], + product_id: int, +) -> int | None: + for index, product in enumerate(storage): + if product[PRODUCT_ID_INDEX] == product_id: + storage.pop(index) + return product_id + + print(f"No product with id {product_id}.") + return None diff --git a/src/part2/storage.py b/src/part2/storage.py new file mode 100644 index 00000000..726ab823 --- /dev/null +++ b/src/part2/storage.py @@ -0,0 +1,12 @@ +from decimal import Decimal +from typing import Final + + +Product = tuple[int, str, Decimal, int] + +PRODUCT_ID_INDEX: Final = 0 +NAME_INDEX: Final = 1 +PRICE_INDEX: Final = 2 +QUANTITY_INDEX: Final = 3 + +PRODUCT_ID_MIN: Final[int] = 1 diff --git a/src/part2/utils.py b/src/part2/utils.py new file mode 100644 index 00000000..28fe897f --- /dev/null +++ b/src/part2/utils.py @@ -0,0 +1,10 @@ +from decimal import ROUND_HALF_UP, Decimal +from typing import Final + + +PRICE_PRECISION: Final[int] = 2 +PRICE_STEP: Final[Decimal] = Decimal(1).scaleb(-PRICE_PRECISION) + + +def normalize_price(price: Decimal) -> Decimal: + return price.quantize(PRICE_STEP, rounding=ROUND_HALF_UP)