Skip to content
19 changes: 11 additions & 8 deletions src/part1/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@
# Все константы, зависящие от ёмкостей *_BITS, должны вычисляться из них.

# Twitter's original Snowflake epoch: 2010-11-04 01:42:54.657 UTC.
EPOCH_MS_DEFAULT: Final[int] = 0
EPOCH_MS_DEFAULT: Final[int] = 1288834974657

# Node identifier used when the caller does not supply one.
NODE_ID_DEFAULT: Final[int] = 0
NODE_ID_DEFAULT: Final[int] = 1

# Width of each field, in bits.
TIMESTAMP_BITS: Final[int] = 0
NODE_ID_BITS: Final[int] = 0
SEQUENCE_ID_BITS: Final[int] = 0
TIMESTAMP_BITS: Final[int] = 41
NODE_ID_BITS: Final[int] = 10
SEQUENCE_ID_BITS: Final[int] = 12

# Largest value each field can hold.
TIMESTAMP_MS_MAX: Final[int] = 0
NODE_ID_MAX: Final[int] = 0
SEQUENCE_ID_MAX: Final[int] = 0
TIMESTAMP_MS_MAX: Final[int] = (1 << TIMESTAMP_BITS) - 1
NODE_ID_MAX: Final[int] = (1 << NODE_ID_BITS) - 1
SEQUENCE_ID_MAX: Final[int] = (1 << SEQUENCE_ID_BITS) - 1

# Здесь можно добавить собственные вспомогательные константы
# (например, сдвиги полей при сборке идентификатора).
SEQUENCE_ID_SHIFT: Final[int] = 0 # справа(младшие разряды)
NODE_ID_SHIFT: Final[int] = SEQUENCE_ID_BITS
TIMESTAMP_SHIFT: Final[int] = SEQUENCE_ID_BITS + NODE_ID_BITS
36 changes: 28 additions & 8 deletions src/part1/snowflake.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@
:func:`decode_node_id` and :func:`decode_sequence_id`.
"""

import time # noqa: F401
import time

from .constants import ( # noqa: F401
from .constants import (
EPOCH_MS_DEFAULT,
NODE_ID_DEFAULT,
NODE_ID_MAX,
NODE_ID_SHIFT,
SEQUENCE_ID_BITS,
SEQUENCE_ID_MAX,
TIMESTAMP_MS_MAX,
TIMESTAMP_SHIFT,
)


Expand All @@ -34,8 +37,7 @@ def read_current_millis(epoch_ms: int) -> int:
The count of whole milliseconds between ``epoch_ms`` and now. May be
negative if ``epoch_ms`` lies in the future.
"""
# TODO: реализуйте функцию
return 0
return int(time.time() * 1000) - epoch_ms


def decode_timestamp_ms(snowflake_id: int, epoch_ms: int = EPOCH_MS_DEFAULT) -> int:
Expand All @@ -51,7 +53,8 @@ def decode_timestamp_ms(snowflake_id: int, epoch_ms: int = EPOCH_MS_DEFAULT) ->
generated.
"""
# TODO: реализуйте функцию
return 0
time_res = snowflake_id >> TIMESTAMP_SHIFT
return time_res + epoch_ms


def decode_node_id(snowflake_id: int) -> int:
Expand All @@ -65,7 +68,8 @@ def decode_node_id(snowflake_id: int) -> int:
``[0, NODE_ID_MAX]``.
"""
# TODO: реализуйте функцию
return 0
node_id = (snowflake_id >> SEQUENCE_ID_BITS) & NODE_ID_MAX
return node_id


def decode_sequence_id(snowflake_id: int) -> int:
Expand All @@ -79,7 +83,8 @@ def decode_sequence_id(snowflake_id: int) -> int:
the range ``[0, SEQUENCE_ID_MAX]``.
"""
# TODO: реализуйте функцию
return 0
sequence_id = snowflake_id & SEQUENCE_ID_MAX
return sequence_id


def generate_snowflake_id(
Expand Down Expand Up @@ -111,4 +116,19 @@ def generate_snowflake_id(
cases an explanatory message is printed to stdout first.
"""
# TODO: реализуйте функцию
return 0
if not (0 <= node_id <= NODE_ID_MAX):
print(f"node_id must be in [0, {NODE_ID_MAX}], but you wrote {node_id}")
return None
if not (0 <= sequence_id <= SEQUENCE_ID_MAX):
print(
f"sequence_id must be in [0, {SEQUENCE_ID_MAX}], but you wrote {sequence_id}"
)
return None
timestamp = read_current_millis(epoch_ms)
if not (0 <= timestamp <= TIMESTAMP_MS_MAX):
print("overflows")
return None
node_res = node_id << NODE_ID_SHIFT
timestamp_res = timestamp << TIMESTAMP_SHIFT
result = timestamp_res | node_res | sequence_id
return result
78 changes: 72 additions & 6 deletions src/part2/cart.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
an explanatory message to stdout and returns ``None``.
"""

from typing import Final
from typing import Final, cast

from .crud import read_product, update_product # noqa: F401
from .storage import ( # noqa: F401
from .crud import read_product, update_product
from .storage import (
NAME_INDEX,
PRICE_INDEX,
QUANTITY_INDEX,
Expand All @@ -24,7 +24,21 @@

# TODO: задайте позиции полей внутри кортежа CartLine
LINE_PRODUCT_ID_INDEX: Final = 0
LINE_QUANTITY_INDEX: Final = 0
LINE_QUANTITY_INDEX: Final = 1


def make_cart(product_id: int, quantity: int) -> CartLine: # переставлять
fields: list[int] = [0] * 2
fields[LINE_PRODUCT_ID_INDEX] = product_id
fields[LINE_QUANTITY_INDEX] = quantity
return cast(CartLine, tuple(fields))


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
return None


def add_to_cart(
Expand Down Expand Up @@ -54,7 +68,31 @@ def add_to_cart(
when the product is unknown or the store cannot cover the request.
"""
# TODO: реализуйте функцию
return (0, 0)
product = read_product(storage, product_id)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше не экономить строки и оставлять пробелы (пустые строки) между логическими блоками. Так читабельность кода улучшается.

if product:
if product[QUANTITY_INDEX] < quantity:
print(
f"not enough stock for product {product_id}: {product[QUANTITY_INDEX]} available, {quantity} requested"
)
return None
product = update_product(
storage,
product_id,
(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Когда ruff делает такие страшные вещи, то стоит задуматься над тем, чтобы вынести какие-то вычисления на отдельные строки (в данном случае можно было бы создать кортеж и положить его в переменную)
А то читать страшно :)

product[NAME_INDEX],
product[PRICE_INDEX],
product[QUANTITY_INDEX] - quantity,
),
)
for line in cart:
if line[LINE_PRODUCT_ID_INDEX] == product_id:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут лучше было бы использовать find_cart_line()
Пункт S1

new_line = make_cart(product_id, line[LINE_QUANTITY_INDEX] + quantity)
cart[cart.index(line)] = new_line
return new_line
new_line = make_cart(product_id, quantity)
cart.append(new_line)
return new_line
return None


def remove_from_cart(
Expand Down Expand Up @@ -86,4 +124,32 @@ def remove_from_cart(
line for the product or holds too few units.
"""
# TODO: реализуйте функцию
return (0, 0)
line = find_cart_line(cart, product_id)
if line is None:
print(f"product {product_id} is not in the cart")
return None
if line[LINE_QUANTITY_INDEX] < quantity:
print(
f"cart holds only {line[LINE_QUANTITY_INDEX]} unit(s) "
f"of product {product_id}, cannot remove {quantity}"
)
return None
product = read_product(storage, product_id)
if product is None:
return None
update_product(
storage,
product_id,
(
product[NAME_INDEX],
product[PRICE_INDEX],
product[QUANTITY_INDEX] + quantity,
),
)
new_quantity = line[LINE_QUANTITY_INDEX] - quantity
if new_quantity == 0:
cart.remove(line)
return make_cart(product_id, 0)
new_line = make_cart(product_id, new_quantity)
cart[cart.index(line)] = new_line
return new_line
62 changes: 55 additions & 7 deletions src/part2/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,31 @@
"""

from decimal import Decimal
from typing import cast

from .storage import ( # noqa: F401
from .storage import (
NAME_INDEX,
PRICE_INDEX,
PRODUCT_ID_INDEX,
PRODUCT_ID_MIN,
QUANTITY_INDEX,
Product,
)
from .utils import normalize_price # noqa: F401
from .utils import normalize_price


def make( # чтобы можно было переставлять
product_id: int,
name: str,
price: Decimal,
quantity: int,
) -> Product:
fields: list[int | str | Decimal] = [0] * 4
fields[PRODUCT_ID_INDEX] = product_id
fields[NAME_INDEX] = name
fields[PRICE_INDEX] = price
fields[QUANTITY_INDEX] = quantity
return cast(Product, tuple(fields))


def generate_product_id(storage: list[Product]) -> int:
Expand All @@ -35,7 +52,12 @@ def generate_product_id(storage: list[Product]) -> int:
empty.
"""
# TODO: реализуйте функцию
return 0
if not storage:
return PRODUCT_ID_MIN
max_id = 0
for product in storage:
max_id = max(max_id, product[PRODUCT_ID_INDEX])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут кстати можно было бы сделать так:

max_id = max(product[PRODUCT_ID_INDEX] for product in storage)

return max_id + 1


def create_product(
Expand All @@ -57,7 +79,15 @@ def create_product(
name is printed.
"""
# TODO: реализуйте функцию
return 0
name, price, quantity = fields
for product in storage:
if product[NAME_INDEX] == name:
print(f"product name '{name}' is already taken")
return None
product_id = generate_product_id(storage)
normalized_price = normalize_price(price)
storage.append(make(product_id, name, normalized_price, quantity))
return product_id


def read_product(storage: list[Product], product_id: int) -> Product | None:
Expand All @@ -73,7 +103,11 @@ def read_product(storage: list[Product], product_id: int) -> Product | None:
printed in that case).
"""
# TODO: реализуйте функцию
return (0, "", Decimal(0), 0)
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(
Expand All @@ -100,7 +134,16 @@ def update_product(
left unchanged and a message is printed).
"""
# TODO: реализуйте функцию
return (0, "", Decimal(0), 0)
name, price, quantity = fields
price = normalize_price(price)
for i in range(len(storage)):
product = storage[i]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Чтобы такое не писать, надо использовать enumerate.
Пункт T5

if product[PRODUCT_ID_INDEX] == product_id:
updated_product = make(product_id, name, price, quantity)
storage[i] = updated_product
return updated_product
print(f"no product with id {product_id}")
return None


def delete_product(storage: list[Product], product_id: int) -> int | None:
Expand All @@ -117,4 +160,9 @@ def delete_product(storage: list[Product], product_id: int) -> int | None:
a message is printed).
"""
# TODO: реализуйте функцию
return 0
for product in storage:
if product[PRODUCT_ID_INDEX] == product_id:

@Avshugan Avshugan Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Цикл:

for index, product in enumerate(storage):
        if product[PRODUCT_ID_INDEX] == product_id:

встречается несколько раз, лучше было бы использовать везде read_product. Код был бы понятнее.
Пункт S1

storage.remove(product)
return product_id
print(f"no product with id {product_id}")
return None
8 changes: 4 additions & 4 deletions src/part2/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@

# TODO: задайте позиции полей внутри кортежа Product
PRODUCT_ID_INDEX: Final = 0
NAME_INDEX: Final = 0
PRICE_INDEX: Final = 0
QUANTITY_INDEX: Final = 0
NAME_INDEX: Final = 1
PRICE_INDEX: Final = 2
QUANTITY_INDEX: Final = 3

# TODO: задайте идентификатор первого товара в пустом хранилище
PRODUCT_ID_MIN: Final[int] = 0
PRODUCT_ID_MIN: Final[int] = 1
8 changes: 4 additions & 4 deletions src/part2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
currency values free of binary floating-point error.
"""

from decimal import ROUND_HALF_UP, Decimal # noqa: F401
from decimal import ROUND_HALF_UP, Decimal
from typing import Final


# TODO: задайте число знаков после запятой и шаг квантования
PRICE_PRECISION: Final[int] = 0
PRICE_STEP: Final = Decimal(0)
PRICE_PRECISION: Final[int] = 2
PRICE_STEP: Final = Decimal(1).scaleb(-PRICE_PRECISION)


def normalize_price(price: Decimal) -> Decimal:
Expand All @@ -26,4 +26,4 @@ def normalize_price(price: Decimal) -> Decimal:
with halves rounded up.
"""
# TODO: реализуйте функцию
return Decimal(0)
return price.quantize(PRICE_STEP, rounding=ROUND_HALF_UP)
Loading