Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions src/part1/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@
# Все константы, зависящие от ёмкостей *_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

# Здесь можно добавить собственные вспомогательные константы
# (например, сдвиги полей при сборке идентификатора).
NODE_ID_SHIFT: Final[int] = SEQUENCE_ID_BITS
TIMESTAMP_SHIFT: Final[int] = NODE_ID_BITS + SEQUENCE_ID_BITS
34 changes: 22 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
from time 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() * 1000 - epoch_ms)


def decode_timestamp_ms(snowflake_id: int, epoch_ms: int = EPOCH_MS_DEFAULT) -> int:
Expand All @@ -50,8 +51,7 @@ 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
return (snowflake_id >> TIMESTAMP_SHIFT) + epoch_ms


def decode_node_id(snowflake_id: int) -> int:
Expand All @@ -64,8 +64,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 +77,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 & SEQUENCE_ID_MAX


def generate_snowflake_id(
Expand Down Expand Up @@ -110,5 +108,17 @@ 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("overflows")
return None

return (elapsed_ms << TIMESTAMP_SHIFT) | (node_id << NODE_ID_SHIFT) | sequence_id
411 changes: 411 additions & 0 deletions src/part4/TASK.md

Large diffs are not rendered by default.

Empty file added src/part4/__init__.py
Empty file.
87 changes: 87 additions & 0 deletions src/part4/cart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Shopping-cart operations layered on top of the product store.

The cart is a plain dictionary mapping each product identifier to the
number of units of that product in the cart. 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 .storage import Product


# TODO: при необходимости добавьте свои вспомогательные функции


def add_to_cart(
storage: dict[str, Product],
product_names: set[str],
cart: dict[str, int],
product_id: str,
quantity: int,
) -> int | 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 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
gains ``quantity`` units of ``product_id`` -- the product is added when
the cart had none of it.

Args:
storage: The product store to draw stock from; modified in place on
success.
product_names: The normalised names in ``storage``; a
quantity-only change never renames a product, so it is left
unchanged here.
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 quantity of ``product_id`` in the cart after the addition, or
``None`` when the product is unknown or the store cannot cover the
request.
"""
# TODO: реализуйте функцию
return 0


def remove_from_cart(
storage: dict[str, Product],
product_names: set[str],
cart: dict[str, int],
product_id: str,
quantity: int,
) -> int | None:
"""Move ``quantity`` units of ``product_id`` from ``cart`` back to the store.

If the cart holds no units of ``product_id``, a message is printed and
nothing changes. If it holds fewer units than requested, an explanatory
message is printed and nothing changes. If the product is missing from
``storage``, a message is printed and nothing changes. Otherwise the cart loses
``quantity`` units of the product -- the product is dropped from the
cart when its quantity reaches zero -- and the store record gains
``quantity`` units back.

Args:
storage: The product store to return stock to; modified in place on
success.
product_names: The normalised names in ``storage``; a
quantity-only change never renames a product, so it is left
unchanged here.
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 quantity of ``product_id`` left in the cart after the removal
(zero means the product was dropped from the cart), or ``None`` when
the cart does not hold the product or holds too few units.
"""
# TODO: реализуйте функцию
return 0
157 changes: 157 additions & 0 deletions src/part4/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"""Create/read/update/delete operations over the in-memory product store.

Every operation takes the store -- a dictionary mapping product id to its
:data:`~src.part4.storage.Product` record -- as its first argument and
works on it in place. ``create_product``, ``update_product`` and
``delete_product`` additionally take ``product_names``, the ``set[str]``
of normalised names currently in ``storage`` (see
:mod:`src.part4.storage`), and keep it in sync with ``storage`` on
every successful call. The failure path never raises: the operation
prints an explanatory message to stdout and returns ``None``.

The identifier of a new product is a freshly generated UUID string
(:func:`generate_product_id`), independent of insertion order or of any
identifier already in ``storage``.

Every product name that reaches the store is passed through
:func:`~src.part4.utils.normalize_product_name` first. A name that
normalises to an empty string is rejected -- the operation prints a
message and changes nothing. Names are also kept unique --
:func:`create_product` refuses a normalised name that is already taken,
and :func:`update_product` refuses one that is already taken by another
product -- via an O(1) membership test against ``product_names`` rather
than a scan over ``storage.values()``.
"""

import uuid # noqa: F401
from typing import Any

from .storage import ( # noqa: F401
NAME_KEY,
PRICE_KEY,
QUANTITY_KEY,
Product,
)
from .utils import normalize_price, normalize_product_name # noqa: F401


def generate_product_id() -> str:
"""Generate an identifier for a new product.

Returns:
A freshly generated UUID, stringified.
"""
# TODO: реализуйте функцию
return ""


def create_product(
storage: dict[str, Product],
product_names: set[str],
fields: dict[str, Any],
) -> str | None:
"""Add a new product to ``storage`` and return its new identifier.

Args:
storage: The product store to add to; modified in place on
success.
product_names: The normalised names already in ``storage``; the
new name is added to it in place on success.
fields: A dictionary with the ``name``, ``price`` and ``quantity``
keys (see :data:`~src.part4.storage.NAME_KEY`,
:data:`~src.part4.storage.PRICE_KEY` and
:data:`~src.part4.storage.QUANTITY_KEY`) describing the
product. ``name`` is normalised (surrounding whitespace
stripped, lower-cased) before it is stored or compared.
``price`` is a :class:`~decimal.Decimal` amount and is rounded
to the stored money precision before it is saved.

Returns:
The identifier generated for the new product, or ``None`` when
``name`` is blank once normalised or a product with the same
normalised name already exists. In the ``None`` case ``storage``
and ``product_names`` are left unchanged and an explanatory
message is printed.
"""
# TODO: реализуйте функцию
return ""


def read_product(storage: dict[str, Product], product_id: str) -> Product | None:
"""Return the product stored under ``product_id``.

Args:
storage: The product store to search.
product_id: The identifier to look up.

Returns:
The matching product record, or ``None`` when no product carries
that identifier (a message is printed in that case).
"""
# TODO: реализуйте функцию
return {}


def update_product(
storage: dict[str, Product],
product_names: set[str],
product_id: str,
fields: dict[str, Any],
) -> Product | None:
"""Overwrite the fields of the product stored under ``product_id``.

The identifier itself is preserved. ``fields`` may carry any subset of
``name``, ``price`` and ``quantity``; a key that is missing keeps the
value the product already has, so a caller can update a single field
without repeating the other two.

Args:
storage: The product store to modify; the matching record is
updated in place on success.
product_names: The normalised names already in ``storage``; on a
successful rename the old name is removed from it and the new
one added.
product_id: The identifier of the product to change.
fields: A dictionary with any of the ``name``, ``price`` and
``quantity`` keys (see :data:`~src.part4.storage.NAME_KEY`,
:data:`~src.part4.storage.PRICE_KEY` and
:data:`~src.part4.storage.QUANTITY_KEY`) holding the new
values. ``price`` is a :class:`~decimal.Decimal` amount and is
rounded to the stored money precision before it is saved.
Mutated in place: a ``name`` or ``price`` value present in
``fields`` is overwritten with its normalised form.

Returns:
The updated product record, or ``None`` when ``name`` is present
and blank once normalised, no product carries that identifier, or
the normalised name is already taken by another product. In every
``None`` case ``storage`` and ``product_names`` are left unchanged
and an explanatory message is printed. Renaming a product to its
own current name is allowed.
"""
# TODO: реализуйте функцию
return {}


def delete_product(
storage: dict[str, Product],
product_names: set[str],
product_id: str,
) -> str | None:
"""Remove the product stored under ``product_id`` from ``storage``.

Args:
storage: The product store to remove from; modified in place on
success.
product_names: The normalised names already in ``storage``; the
departing product's name is removed from it in place on
success.
product_id: The identifier of the product to remove.

Returns:
``product_id`` when a product was removed, or ``None`` when no
product carried that identifier (``storage`` and ``product_names``
are left unchanged and a message is printed).
"""
# TODO: реализуйте функцию
return ""
Loading
Loading