diff --git a/Solve task 1/constants.py b/Solve task 1/constants.py new file mode 100644 index 00000000..203dcc4c --- /dev/null +++ b/Solve task 1/constants.py @@ -0,0 +1,11 @@ +from typing import Final +import time + +EPOCH_MS_DEFAULT: Final[int] = 1288834974657 +NODE_ID_DEFAULT: Final[int] = 1 +TIMESTAMP_BITS: Final[int] = 41 +NODE_ID_BITS: Final[int] = 10 +SEQUENCE_ID_BITS: Final[int] = 12 +TIMESTAMP_MS_MAX: Final[int] = 2**TIMESTAMP_BITS - 1 +NODE_ID_MAX: Final[int] = 2**NODE_ID_BITS - 1 +SEQUENCE_ID_MAX: Final[int] = 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..45bf7715 --- /dev/null +++ b/Solve task 1/snowflake.py @@ -0,0 +1,32 @@ +import time + +from constants import * + +def read_current_millis(epoch_ms: int) -> int: + return int(time.time()*1000) - epoch_ms + +def decode_timestamp_ms(snowflake_id: int, epoch_ms: int = EPOCH_MS_DEFAULT) -> int: + return int(bin(snowflake_id)[3:44], 2) + epoch_ms + +def decode_node_id(snowflake_id: int) -> int: + return int(bin(snowflake_id)[44:54], 2) + +def decode_sequence_id(snowflake_id: int) -> int: + return int(bin(snowflake_id)[54:], 2) + +def generate_snowflake_id( + sequence_id: int, + node_id: int = NODE_ID_DEFAULT, + epoch_ms: int = EPOCH_MS_DEFAULT) -> int | None: + if node_id < 0 or node_id > NODE_ID_MAX: + print (f"node_id must be in [0, {NODE_ID_MAX}]") + return None + elif sequence_id < 0 or sequence_id > SEQUENCE_ID_MAX: + print (f"sequence_id be in [0, {SEQUENCE_ID_MAX}]") + return None + else: + timestamp = time.time() + if timestamp > TIMESTAMP_MS_MAX: + print ("overflows") + return None + return int('0' + bin(int(time.time()*1000) + epoch_ms)[2:].zfill(41) + bin(node_id)[2:].zfill(10) + bin(sequence_id)[2:].zfill(12), 2) diff --git a/Solve task 2/cart.py b/Solve task 2/cart.py new file mode 100644 index 00000000..26891a75 --- /dev/null +++ b/Solve task 2/cart.py @@ -0,0 +1,67 @@ +from typing import Final +from crud import read_product, update_product +from storage import ( + NAME_INDEX, + PRICE_INDEX, + QUANTITY_INDEX, + Product, +) + + +type CartLine = tuple[int, int] + +# TODO: задайте позиции полей внутри кортежа CartLine +LINE_PRODUCT_ID_INDEX: Final = 0 +LINE_QUANTITY_INDEX: Final = 1 + + +def add_to_cart( + storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int, +) -> CartLine | None: + prod = read_product(storage, product_id) + if prod is None: + return None + if prod[QUANTITY_INDEX] < quantity: + print (f"not enough stock for product {product_id}: {prod[QUANTITY_INDEX]} available, {quantity} requested") + return None + storage[storage.index(prod)] = update_product(storage, product_id, (prod[NAME_INDEX], prod[PRICE_INDEX], prod[QUANTITY_INDEX] - quantity)) + for i in range(len(cart)): + if cart[i][LINE_PRODUCT_ID_INDEX] is product_id: + cart[i] = (product_id, cart[i][LINE_QUANTITY_INDEX] + quantity) + return cart[i] + cart.append(product_id, quantity) + return cart[-1] + + +def find_cart_line(cart: list[CartLine], product_id: int) -> CartLine | None: + for line in cart: + if line[LINE_PRODUCT_ID_INDEX] is product_id: + return line + return None + + +def remove_from_cart( + storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int, +) -> CartLine | None: + prod_cart = find_cart_line(cart, product_id) + if prod_cart is None: + print (f"product {product_id} is not in the cart") + return None + if prod_cart[LINE_QUANTITY_INDEX] < quantity: + print (f"cart holds only {prod_cart[LINE_QUANTITY_INDEX]} unit(s) of product {product_id}, cannot remove {quantity}") + return None + prod = read_product(storage, product_id) + if prod is None: + return None + storage[storage.index(prod)] = update_product(storage, product_id, (prod[NAME_INDEX], prod[PRICE_INDEX], prod[QUANTITY_INDEX] + quantity)) + if (prod_cart[LINE_QUANTITY_INDEX] - quantity) == 0: + cart.remove(prod_cart) + return 0 + cart [cart.index(prod_cart)] = CartLine(product_id, prod_cart[LINE_QUANTITY_INDEX] - quantity) + return cart [cart.index(prod_cart)] \ No newline at end of file diff --git a/Solve task 2/crud.py b/Solve task 2/crud.py new file mode 100644 index 00000000..5b6519d2 --- /dev/null +++ b/Solve task 2/crud.py @@ -0,0 +1,55 @@ +from decimal import Decimal + +from storage import ( + NAME_INDEX, + PRODUCT_ID_INDEX, + PRODUCT_ID_MIN, + Product, +) +from utils import normalize_price + + +def generate_product_id(storage: list[Product]) -> int: + if not storage: + return 0 + return max(storage)[PRODUCT_ID_INDEX] + 1 + + +def create_product(storage: list[Product], fields: tuple[str, Decimal, int]) -> int | None: + for prod in storage: + if fields [0] is prod[NAME_INDEX]: + print (f"product name '{fields [0]}' is already taken") + return None + new_product = (generate_product_id(storage), fields[0], normalize_price(fields[1]), fields[2]) + storage.append(new_product) + return new_product[0] + + +def read_product(storage: list[Product], product_id: int) -> Product | None: + for prod in storage: + if prod[0] is product_id: + return prod + 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 i in range(len(storage)): + if storage[i][PRODUCT_ID_INDEX] is product_id: + storage[i] = (product_id, fields[0], normalize_price(fields[1]), fields[2]) + return storage[i] + print (f"no product with id {product_id}") + return None + + +def delete_product(storage: list[Product], product_id: int) -> int | None: + for i in range(len(storage)): + if storage[i][PRODUCT_ID_INDEX] is product_id: + del storage[i] + 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..c9c544d8 --- /dev/null +++ b/Solve task 2/storage.py @@ -0,0 +1,12 @@ +from decimal import Decimal +from typing import Final + + +type 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 \ No newline at end of file diff --git a/Solve task 2/utils.py b/Solve task 2/utils.py new file mode 100644 index 00000000..67364b10 --- /dev/null +++ b/Solve task 2/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(1).scaleb(-PRICE_PRECISION) + + +def normalize_price(price: Decimal) -> Decimal: + return price.quantize(PRICE_STEP, rounding=ROUND_HALF_UP) \ No newline at end of file diff --git a/Solve task 3/crud.py b/Solve task 3/crud.py new file mode 100644 index 00000000..af5f69e5 --- /dev/null +++ b/Solve task 3/crud.py @@ -0,0 +1,69 @@ +from decimal import Decimal +from storage import ( + NAME_INDEX, + PRODUCT_ID_INDEX, + PRODUCT_ID_MIN, + Product, +) +from utils import normalize_price, normalize_product_name + + +def generate_product_id(storage: list[Product]) -> int: + if not storage: + return PRODUCT_ID_MIN + return max(storage)[PRODUCT_ID_INDEX] + 1 + + +def create_product(storage: list[Product], fields: tuple[str, Decimal, int]) -> int | None: + fields = (normalize_product_name(fields[0]), fields[1], fields[2]) + if fields[0] == "": + print ("product name must not be blank") + return None + for prod in storage: + if fields[0] == prod[NAME_INDEX]: + print (f"product name '{fields [0]}' is already taken") + return None + new_product = (generate_product_id(storage), fields[0], normalize_price(fields[1]), fields[2]) + storage.append(new_product) + return new_product[0] + + +def read_product(storage: list[Product], product_id: int) -> Product | None: + for prod in storage: + if prod[0] is product_id: + return prod + 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: + norm_name = normalize_product_name(fields[0]) + if norm_name == "": + print ("product name must not be blank") + return None + prod = 0 + for i in range(len(storage)): + if storage[i][NAME_INDEX] == norm_name and storage[i][PRODUCT_ID_INDEX] != product_id: + print (f"product name '{norm_name}' is already taken") + return None + elif storage[i][PRODUCT_ID_INDEX] == product_id: + prod = (product_id, norm_name, normalize_price(fields[1]), fields[2]) + prod_num = i + if prod != 0: + storage[prod_num] = prod + return prod + print (f"no product with id {product_id}") + return None + + +def delete_product(storage: list[Product], product_id: int) -> int | None: + for i in range(len(storage)): + if storage[i][PRODUCT_ID_INDEX] is product_id: + del storage[i] + return product_id + print (f"no product with id {product_id}") + return None \ No newline at end of file diff --git a/Solve task 3/run_admin.py b/Solve task 3/run_admin.py new file mode 100644 index 00000000..81e03643 --- /dev/null +++ b/Solve task 3/run_admin.py @@ -0,0 +1,94 @@ +from decimal import Decimal, InvalidOperation +from typing import Final + +from crud import ( + create_product, + delete_product, + read_product, + update_product, +) +from storage import Product +from utils import get_storage_str_representation + + +PROMPT: Final[str] = "admin> " +HELP_TEXT: Final[str] = """Available commands: + help show this message + exit leave the console + show print the whole store as a table + create add a product, print its new id + read print the product with that id + update overwrite that product's fields + delete remove the product with that id + +For create and update the price and quantity are the last two words of the +line; everything before them is the product name, so it may contain spaces +(for example "Gibson SG Junior") and needs no quoting.""" + + +def show_help() -> None: + print(HELP_TEXT) + + +def print_result(result: object) -> None: + if result: print (result) + + + +def run_command(storage: list[Product], line: str) -> bool: + line = line.split() + match line: + case ["help"]: + show_help() + return True + case ["exit"]: + return False + case ["show"]: + get_storage_str_representation(storage) + return True + case ["create", *_, price, quantity]:#попробовать заменить на line[-1] и line[-2] price и quantity + name = " ".join(line[1:-2]) + pocket = create_product(storage, (name, Decimal(price), int(quantity))) + if pocket: + print (pocket) + return True + case ["read", id_num]: + output = read_product(storage, int(id_num)) + if output: + print (output) + return True + case ["update", id_num, *_, price, quantity]:#попробовать заменить на line[-1] и line[-2] price и quantity + name = " ".join(line[2:-2]) + pocket = update_product (storage, int(id_num), (name, Decimal(price), int(quantity))) + if pocket: + print (pocket) + return True + case ["delete", id_num]: + pocket = delete_product(storage, int(id_num)) + if pocket: + print (pocket) + return True + case _: + line = " ".join(line) + print (f"'{line}' is not a command") + return True + + + +def main() -> None: + + storage: list[Product] = [] + running = True + + while running: + line = input(PROMPT).strip() + + try: + running = run_command(storage, line) + + except (ValueError): + print(f"{line!r} names a command but its arguments are invalid") + + +if __name__ == "__main__": + main() diff --git a/Solve task 3/storage.py b/Solve task 3/storage.py new file mode 100644 index 00000000..c9c544d8 --- /dev/null +++ b/Solve task 3/storage.py @@ -0,0 +1,12 @@ +from decimal import Decimal +from typing import Final + + +type 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 \ No newline at end of file diff --git a/Solve task 3/utils.py b/Solve task 3/utils.py new file mode 100644 index 00000000..46b283af --- /dev/null +++ b/Solve task 3/utils.py @@ -0,0 +1,41 @@ +from decimal import ROUND_HALF_UP, Decimal +from typing import Final +from storage import Product + +PRICE_PRECISION: Final[int] = 2 +PRICE_STEP: Final = Decimal(1).scaleb(-PRICE_PRECISION) +TABLE_HEADERS: Final[tuple[str, ...]] = ("ID", "name", "price", "quantity") + + +def normalize_price(price: Decimal) -> Decimal: + return price.quantize(PRICE_STEP, rounding=ROUND_HALF_UP) + + +def normalize_product_name(name: str) -> str: + return (" ".join(name.split())).lower() + + +def size_check(storage: list[Product]):#находит нужные размеры столбцов + column_size = [[2], [4], [5], [8]] + for prod in storage: + for ind in range (4): + column_size[ind].append(len(str(prod[ind]))) + return [max(column_size[i]) for i in range(4)] + +def output_line(values: str|Product, size: list[int]):#выводит строку таблицы + if values == "-": + line = ("|" + "-" * (2 + size[0]) + "|" + "-" * (2 + size[1]) + + "|" + "-" * (2 + size[2]) + "|" + "-" * (2 + size[3]) + "|") + else: + line = "|" + for ind in range(4): + value = str(values[ind]) + line += f" {value} " + " " * (size[ind] - len(value)) + "|" + print (line) + +def get_storage_str_representation(storage: list[Product]) -> str: + final_size = size_check(storage) + output_line(TABLE_HEADERS, final_size) + output_line("-", final_size) + for prod in storage: + output_line(prod, final_size) \ No newline at end of file