Skip to content
16 changes: 8 additions & 8 deletions src/part1/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@
# Все константы, зависящие от ёмкостей *_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] = 2**TIMESTAMP_BITS - 1
NODE_ID_MAX: Final[int] = 2**NODE_ID_BITS - 1
SEQUENCE_ID_MAX: Final[int] = 2**SEQUENCE_ID_BITS - 1

# Здесь можно добавить собственные вспомогательные константы
# (например, сдвиги полей при сборке идентификатора).
107 changes: 23 additions & 84 deletions src/part1/snowflake.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,6 @@
"""Twitter Snowflake identifier generator.
import time

Snowflake produces roughly time-ordered 64-bit integer identifiers without
coordination between nodes. Each identifier packs the milliseconds elapsed
since a custom epoch, a node identifier and a per-millisecond sequence counter
into a single 63-bit positive integer (see ``constants`` for the layout).

The public entry point is :func:`generate_snowflake_id`. It is stateless: the
caller passes the sequence counter on every call and is responsible for
advancing it within a millisecond and resetting it when the clock ticks over.

Each packed field can be read back on its own with :func:`decode_timestamp_ms`,
:func:`decode_node_id` and :func:`decode_sequence_id`.
"""

import time # noqa: F401

from .constants import ( # noqa: F401
from .constants import (
EPOCH_MS_DEFAULT,
NODE_ID_DEFAULT,
NODE_ID_MAX,
Expand All @@ -25,90 +10,44 @@


def read_current_millis(epoch_ms: int) -> int:
"""Read the number of milliseconds elapsed since the custom epoch.

Args:
epoch_ms: The custom epoch expressed as Unix milliseconds.
a = int(time.time() * 1000)

Returns:
The count of whole milliseconds between ``epoch_ms`` and now. May be
negative if ``epoch_ms`` lies in the future.
"""
# TODO: реализуйте функцию
return 0
return a - epoch_ms


def decode_timestamp_ms(snowflake_id: int, epoch_ms: int = EPOCH_MS_DEFAULT) -> int:
"""Read the timestamp field out of a Snowflake identifier.

Args:
snowflake_id: An identifier produced against the same epoch.
epoch_ms: The epoch the identifier was generated against. Defaults to
the original Twitter epoch (2010-11-04 01:42:54.657 UTC).
a = snowflake_id >> 22

Returns:
The absolute Unix time in milliseconds at which the identifier was
generated.
"""
# TODO: реализуйте функцию
return 0
return a + epoch_ms


def decode_node_id(snowflake_id: int) -> int:
"""Read the node identifier field out of a Snowflake identifier.
a = (snowflake_id >> 12) & 0b1111111111

Args:
snowflake_id: An identifier produced by :func:`generate_snowflake_id`.

Returns:
The node identifier packed into ``snowflake_id``, in the range
``[0, NODE_ID_MAX]``.
"""
# TODO: реализуйте функцию
return 0
return a


def decode_sequence_id(snowflake_id: int) -> int:
"""Read the sequence counter field out of a Snowflake identifier.

Args:
snowflake_id: An identifier produced by :func:`generate_snowflake_id`.
a = snowflake_id & 0b111111111111

Returns:
The per-millisecond sequence counter packed into ``snowflake_id``, in
the range ``[0, SEQUENCE_ID_MAX]``.
"""
# TODO: реализуйте функцию
return 0
return a


def generate_snowflake_id(
sequence_id: int,
node_id: int = NODE_ID_DEFAULT,
epoch_ms: int = EPOCH_MS_DEFAULT,
) -> int | None:
"""Build and return a Snowflake identifier for the current millisecond.

The function is stateless: the caller passes the per-millisecond sequence
counter explicitly. It is the caller's responsibility to increment
``sequence_id`` for identifiers minted within the same millisecond and to
reset it once the clock advances.

Args:
sequence_id: The per-millisecond sequence counter, in the range
``[0, SEQUENCE_ID_MAX]``.
node_id: The identifier of this node, in the range ``[0, NODE_ID_MAX]``.
Optional; defaults to ``NODE_ID_DEFAULT``.
epoch_ms: The start of the epoch as Unix milliseconds. Optional;
defaults to the original Twitter epoch (2010-11-04 01:42:54.657
UTC).

Returns:
The Snowflake identifier as a positive 63-bit integer, or ``None`` if
``node_id`` is outside ``[0, NODE_ID_MAX]``, ``sequence_id`` is outside
``[0, SEQUENCE_ID_MAX]``, or the elapsed time no longer fits in the
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 node_id > NODE_ID_MAX or node_id < 0:
m = f"node_id must be in [0,{NODE_ID_MAX}]"
print(m)
return None
if sequence_id > SEQUENCE_ID_MAX or sequence_id < 0:
m = f"sequence_id must be in [0,{SEQUENCE_ID_MAX}]"
print(m)
return None
if epoch_ms > TIMESTAMP_MS_MAX and epoch_ms < 0:
m = "overflows"
print(m)
return None
return read_current_millis(epoch_ms) << 22 | node_id << 12 | sequence_id
131 changes: 65 additions & 66 deletions src/part2/cart.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
"""Shopping-cart operations layered on top of the product store.

The cart is a plain list of tuples, one :data:`CartLine` --
``(product_id, quantity)`` -- per distinct product. Moving units between the
store and the cart keeps the two sides in balance: :func:`add_to_cart` takes
units out of stock, :func:`remove_from_cart` puts them back.

Like the CRUD layer, the failure path never raises -- the operation prints
an explanatory message to stdout and returns ``None``.
"""

from typing import Final

from .crud import read_product, update_product # noqa: F401
from .storage import ( # noqa: F401
NAME_INDEX,
PRICE_INDEX,
QUANTITY_INDEX,
Product,
)
from .crud import read_product
from .storage import Product


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 i in range(len(cart)):
if cart[i][0] == product_id:
return cart[i]
print(f"product {product_id} is not in the cart")
return None


def add_to_cart(
Expand All @@ -33,28 +24,31 @@ def add_to_cart(
product_id: int,
quantity: int,
) -> CartLine | None:
"""Move ``quantity`` units of ``product_id`` from the store into ``cart``.

The product is looked up in ``storage``. If it is missing, a message is
printed (by :func:`~src.part2.crud.read_product`) and nothing
changes. If the store holds fewer units than requested, an explanatory
message is printed and nothing changes. Otherwise the store record is
decremented by ``quantity`` and the cart line for ``product_id`` gains
``quantity`` units -- a new line is created when the cart had none.

Args:
storage: The product store to draw stock from; modified in place on
success.
cart: The cart to add the units to; modified in place on success.
product_id: The identifier of the product to add.
quantity: Number of units to move into the cart.

Returns:
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 product is None:
return None
else:
if product[3] < quantity:
print(
f"not enough stock for product {product_id}: {product[3]} available, {quantity} requested"
)
return None
else:
indp = storage.index(product)
storage[indp] = (
storage[indp][0],
storage[indp][1],
storage[indp][2],
storage[indp][3] - quantity,
)
for i in range(len(cart)):
if cart[i][0] == product_id:
idn, quantit1 = cart[i]
cart[i] = (idn, quantit1 + quantity)
return cart[i]
cart1 = (product_id, quantity)
cart.append(cart1)
return cart1


def remove_from_cart(
Expand All @@ -63,27 +57,32 @@ def remove_from_cart(
product_id: int,
quantity: int,
) -> CartLine | None:
"""Move ``quantity`` units of ``product_id`` from ``cart`` back to the store.

The cart line for ``product_id`` is looked up. If there is none, a
message is printed and nothing changes. If the line holds fewer units
than requested, an explanatory message is printed and nothing changes.
Otherwise the cart line loses ``quantity`` units -- the line is dropped
when it reaches zero -- and the store record gains ``quantity`` units
back.

Args:
storage: The product store to return stock to; modified in place on
success.
cart: The cart to take the units from; modified in place on
success.
product_id: The identifier of the product to remove.
quantity: Number of units to move back into the store.

Returns:
The cart line for ``product_id`` after the removal (a quantity of
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)
cart1 = find_cart_line(cart, product_id)
product = read_product(storage, product_id)
if cart1 is None:
return None
else:
if cart1[1] < quantity:
print(
f"cart holds only {cart1[1]} unit(s) of product {product_id}, cannot remove {quantity}"
)
return None
else:
if product is None:
return None
else:
indp = storage.index(product)
jdnc = cart.index(cart1)
storage[indp] = (
storage[indp][0],
storage[indp][1],
storage[indp][2],
storage[indp][3] + quantity,
)
cart[jdnc] = (cart[jdnc][0], cart[jdnc][1] - quantity)
idn = cart[jdnc][0]
if (cart[jdnc][1]) == 0:
del cart[jdnc]
return (idn, 0)
else:
return cart[jdnc]
Loading
Loading