45. Реализация банкомата с внесением и выдачей купюр-2
Условие задачи:
Реализовать класс ““Банкомат””
Есть валюты:
currencies = [RUB, USD, EUR]
Монеты/купюры каждой валюты бывают ТОЛьКО такого номинала:
nominal = [1, 5, 10, 50, 100, 500, 1000, 5000]
Нужно разработать класс ““Банкомат”” со следующими возможностями:
Принять в банкомат несколько монет/купюр:
put(currency, nominal, count) -> None
Выдать из банкомата определенную сумму денег в определенной валюте:
get(currency, amount) -> [(nominal, count), …] | raise Unavailable Error
Ответом должен быть список, каким образом банкомат выдаст нужную сумму, какими монетами/купюрами, в
каком количестве.
Банкомат обычный, сам деньги не печатает, может выдавать только то, что в него положили.
Если выдать требуемую сумму нет возможности - бросаем исключение Unavailable Error.
Спойлеры к решению
Подсказки
- Деньги нужно хранить отдельно по каждой валюте.
- Для хранения удобно использовать вложенный словарь:
currency -> nominal -> count. - Метод
put()должен проверять валюту, номинал и количество. - Метод
get()должен собрать нужную сумму только из доступных купюр/монет. - Выдачу можно собирать от большего номинала к меньшему.
- Состояние банкомата нужно менять только после того, как сумма точно собрана.
- Если сумму собрать нельзя, нужно выбросить
UnavailableError.
Решение
class UnavailableError(Exception):
pass
class ATM:
currencies = {"RUB", "USD", "EUR"}
nominals = [1, 5, 10, 50, 100, 500, 1000, 5000]
def __init__(self):
self.storage = {
currency: {
nominal: 0
for nominal in self.nominals
}
for currency in self.currencies
}
def put(self, currency: str, nominal: int, count: int) -> None:
self._validate_currency(currency)
self._validate_nominal(nominal)
if count <= 0:
raise ValueError("count must be positive")
self.storage[currency][nominal] += count
def get(self, currency: str, amount: int) -> list[tuple[int, int]]:
self._validate_currency(currency)
if amount <= 0:
raise ValueError("amount must be positive")
result = []
remaining = amount
for nominal in sorted(self.nominals, reverse=True):
available_count = self.storage[currency][nominal]
if available_count == 0:
continue
need_count = remaining // nominal
take_count = min(need_count, available_count)
if take_count > 0:
result.append((nominal, take_count))
remaining -= nominal * take_count
if remaining != 0:
raise UnavailableError("requested amount is unavailable")
for nominal, count in result:
self.storage[currency][nominal] -= count
return result
def _validate_currency(self, currency: str) -> None:
if currency not in self.currencies:
raise ValueError("unsupported currency")
def _validate_nominal(self, nominal: int) -> None:
if nominal not in self.nominals:
raise ValueError("unsupported nominal")
Пример использования:
atm = ATM()
atm.put("RUB", 1000, 2)
atm.put("RUB", 500, 3)
atm.put("RUB", 100, 10)
print(atm.get("RUB", 2600))
Результат:
[(1000, 2), (500, 1), (100, 1)]
После успешной выдачи купюры списываются из банкомата:
print(atm.storage["RUB"])
Если нужную сумму выдать невозможно:
atm = ATM()
atm.put("USD", 100, 1)
print(atm.get("USD", 150))
будет выброшено исключение:
UnavailableError: requested amount is unavailable
Потому что в банкомате есть только одна купюра 100 USD, а сумму 150 USD из доступных купюр собрать нельзя.
Главная логика метода get():
1. Проверяем валюту и сумму.
2. Идём по номиналам от большего к меньшему.
3. Берём столько купюр текущего номинала, сколько нужно и сколько доступно.
4. Уменьшаем остаток суммы.
5. Если остаток не равен 0, выдача невозможна.
6. Если сумма собрана, списываем купюры из хранилища.
7. Возвращаем список пар: (номинал, количество).
Важно: списание из storage происходит только после успешной сборки всей суммы. Поэтому если возникает UnavailableError, состояние банкомата не меняется.