Skip to content
Open
21 changes: 13 additions & 8 deletions src/part1/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,25 @@
# Все константы, зависящие от ёмкостей *_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

# Здесь можно добавить собственные вспомогательные константы
# (например, сдвиги полей при сборке идентификатора).

# Shift amounts
SEQUENCE_ID_SHIFT = 0
NODE_ID_SHIFT = SEQUENCE_ID_BITS
TIMESTAMP_SHIFT = SEQUENCE_ID_BITS + NODE_ID_BITS
43 changes: 31 additions & 12 deletions src/part1/snowflake.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
: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_MAX,
TIMESTAMP_MS_MAX,
TIMESTAMP_SHIFT,
)


Expand All @@ -34,8 +36,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 @@ -50,8 +51,8 @@ def decode_timestamp_ms(snowflake_id: int, epoch_ms: int = EPOCH_MS_DEFAULT) ->
The absolute Unix time in milliseconds at which the identifier was
generated.
"""
# TODO: реализуйте функцию
return 0
stored_ms = (snowflake_id >> TIMESTAMP_SHIFT) & TIMESTAMP_MS_MAX
return stored_ms + epoch_ms


def decode_node_id(snowflake_id: int) -> int:
Expand All @@ -64,8 +65,7 @@ def decode_node_id(snowflake_id: int) -> int:
The node identifier packed into ``snowflake_id``, in the range
``[0, NODE_ID_MAX]``.
"""
# TODO: реализуйте функцию
return 0
return (snowflake_id >> NODE_ID_SHIFT) & NODE_ID_MAX


def decode_sequence_id(snowflake_id: int) -> int:
Expand All @@ -78,8 +78,7 @@ def decode_sequence_id(snowflake_id: int) -> int:
The per-millisecond sequence counter packed into ``snowflake_id``, in
the range ``[0, SEQUENCE_ID_MAX]``.
"""
# TODO: реализуйте функцию
return 0
return (snowflake_id >> 0) & SEQUENCE_ID_MAX


def generate_snowflake_id(
Expand Down Expand Up @@ -110,5 +109,25 @@ def generate_snowflake_id(
timestamp field (roughly 69 years after ``epoch_ms``). In each of those
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}], got {node_id}")
return None

if not (0 <= sequence_id <= SEQUENCE_ID_MAX):
print(f"sequence_id must be in [0, {SEQUENCE_ID_MAX}], got {sequence_id}")
return None

elapsed_ms = read_current_millis(epoch_ms)
if elapsed_ms > TIMESTAMP_MS_MAX:
print(
f"timestamp overflows: elapsed {elapsed_ms} ms exceeds \n\
TIMESTAMP_MS_MAX ({TIMESTAMP_MS_MAX})"
)
return None

snowflake_id = (
(elapsed_ms << TIMESTAMP_SHIFT)
| (node_id << NODE_ID_SHIFT)
| (sequence_id << 0)
)
return snowflake_id
71 changes: 63 additions & 8 deletions src/part2/cart.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

from typing import Final

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 @@ -22,9 +22,15 @@

type CartLine = tuple[int, int]

# TODO: задайте позиции полей внутри кортежа CartLine
LINE_PRODUCT_ID_INDEX: Final = 0
LINE_QUANTITY_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
return None


def add_to_cart(
Expand Down Expand Up @@ -53,8 +59,30 @@ def add_to_cart(
The cart line for ``product_id`` after the addition, or ``None``
when the product is unknown or the store cannot cover the request.
"""
# TODO: реализуйте функцию
return (0, 0)
product = read_product(storage, product_id)
if not product:
return None
if product[QUANTITY_INDEX] < quantity:
print(
f"not enough stock for product {product_id}: {product[QUANTITY_INDEX]} available, {quantity} requested"
)
return None
update_product(
storage,
product_id,
(product[NAME_INDEX], product[PRICE_INDEX], product[QUANTITY_INDEX] - quantity),
)

line = find_cart_line(cart, product_id)
if line:
cart.remove(line)
updated_line = (product_id, line[LINE_QUANTITY_INDEX] + quantity)
cart.append(updated_line)
return updated_line
else:
new_line = (product_id, quantity)
cart.append(new_line)
return new_line


def remove_from_cart(
Expand Down Expand Up @@ -85,5 +113,32 @@ def remove_from_cart(
zero means the line was dropped), or ``None`` when the cart has no
line for the product or holds too few units.
"""
# TODO: реализуйте функцию
return (0, 0)
line = find_cart_line(cart, product_id)
if not line:
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) of product {product_id}, cannot remove {quantity}"
)
return None

product = read_product(storage, product_id)
if not product:
print("read_product")
return None

update_product(
storage,
product_id,
(product[NAME_INDEX], product[PRICE_INDEX], product[QUANTITY_INDEX] + quantity),
)

cart.remove(line)
if line[LINE_QUANTITY_INDEX] - quantity == 0:
return (product_id, 0)

updated_line = (product_id, line[LINE_QUANTITY_INDEX] - quantity)
cart.append(updated_line)
return updated_line
58 changes: 46 additions & 12 deletions src/part2/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@

from decimal import Decimal

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 generate_product_id(storage: list[Product]) -> int:
Expand All @@ -34,8 +36,9 @@ def generate_product_id(storage: list[Product]) -> int:
:data:`~src.part2.storage.PRODUCT_ID_MIN` when ``storage`` is
empty.
"""
# TODO: реализуйте функцию
return 0
if not storage:
return PRODUCT_ID_MIN
return max(product[PRODUCT_ID_INDEX] for product in storage) + 1


def create_product(
Expand All @@ -56,8 +59,17 @@ def create_product(
``storage`` is left unchanged and a message naming the clashing
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)
price = normalize_price(price)
storage.append((product_id, name, price, quantity))
return product_id


def read_product(storage: list[Product], product_id: int) -> Product | None:
Expand All @@ -72,8 +84,16 @@ def read_product(storage: list[Product], product_id: int) -> Product | None:
``None`` when no product carries that identifier (a message is
printed in that case).
"""
# TODO: реализуйте функцию
return (0, "", Decimal(0), 0)
for product in storage:
if product[PRODUCT_ID_INDEX] == product_id:
return (
product[PRODUCT_ID_INDEX],
product[NAME_INDEX],
product[PRICE_INDEX],
product[QUANTITY_INDEX],
)
print(f"no product with id {product_id}")
return None


def update_product(
Expand All @@ -99,8 +119,18 @@ def update_product(
``None`` when no product carries that identifier (``storage`` is
left unchanged and a message is printed).
"""
# TODO: реализуйте функцию
return (0, "", Decimal(0), 0)
for ind, product in enumerate(storage):
if product[PRODUCT_ID_INDEX] == product_id:
updated_product = (
product_id,
fields[0],
normalize_price(fields[1]),
fields[2],
)
storage[ind] = updated_product
return storage[ind]
print(f"no product with id {product_id}")
return None


def delete_product(storage: list[Product], product_id: int) -> int | None:
Expand All @@ -116,5 +146,9 @@ def delete_product(storage: list[Product], product_id: int) -> int | None:
product carried that identifier (``storage`` is left unchanged and
a message is printed).
"""
# TODO: реализуйте функцию
return 0
for product in storage:
if product[PRODUCT_ID_INDEX] == product_id:
storage.remove(product)
return product_id
print(f"no product with id {product_id}")
return None
12 changes: 6 additions & 6 deletions src/part2/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@

type Product = tuple[int, str, Decimal, int]

# 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
10 changes: 4 additions & 6 deletions src/part2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
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 @@ -25,5 +24,4 @@ def normalize_price(price: Decimal) -> Decimal:
``price`` quantised to :data:`PRICE_PRECISION` fractional digits,
with halves rounded up.
"""
# TODO: реализуйте функцию
return Decimal(0)
return price.quantize(PRICE_STEP, ROUND_HALF_UP)
Loading
Loading