43. Реализация банкомата с внесением и выдачей купюр
Условие задачи:
Реализовать класс ““Банкомат””
Есть валюты:
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 = amount
for nominal in sorted(self.nominals, reverse=True):
available_count = self.storage[currency][nominal]
if available_count == 0:
continue
needed_count = remaining_amount // nominal
count_to_take = min(available_count, needed_count)
if count_to_take > 0:
result.append((nominal, count_to_take))
remaining_amount -= nominal * count_to_take
if remaining_amount != 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"])
Например, после выдачи 2600 рублей в банкомате останется меньше купюр номиналом 1000, 500 и 100.
Если нужную сумму выдать невозможно:
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. Уменьшаем остаток суммы.
6. Если остаток стал 0 — выдача возможна.
7. Если после прохода остаток не 0 — бросаем UnavailableError.
8. Только после успешной проверки списываем купюры из хранилища.
Важно, что списание происходит только после того, как сумма успешно собрана. Если сумму собрать нельзя, состояние банкомата не меняется.