Для заданных номиналов можно идти от крупных купюр к мелким, сначала рассчитать выдачу, а затем списать остатки. Операция должна быть атомарной, валюты — храниться раздельно.
* Банкомат.
Для заданных номиналов можно идти от крупных купюр к мелким, сначала рассчитать выдачу, а затем списать остатки. Операция должна быть атомарной, валюты — храниться раздельно.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
/**
- Банкомат.
- Инициализируется набором купюр и валюты (например, 1000 рублей, 500 евро).
- При выдаче купюры списываются со склада банкомата.
- Допустимые номиналы:
- --- 50Р, 100Р, 500Р, 1000Р, 5000Р
- --- 20 EUR, 100 EUR, 500 EUR
- Каждая валюта обрабатывается отдельно.
- Банкомат может использоваться многими пользователями одновременно.
- Поддержку многопоточности можно не реализовывать (но будет плюсом). */ class ATM { public ??? withdraw(???) { // TODO implement } }
Ответ
Для заданных номиналов можно идти от крупных купюр к мелким, сначала рассчитать выдачу, а затем списать остатки. Операция должна быть атомарной, валюты — храниться раздельно.
В этих наборах каждый больший номинал кратен следующему меньшему, поэтому жадный выбор подходит даже с ограниченными остатками. Для произвольных номиналов это неверно и нужен поиск комбинации. Пример Java 10+ копирует входной запас и синхронизирует выдачу:
import java.util.*;
final class ATM {
enum Currency { RUB, EUR }
private static final Map<Currency, List<Integer>> DENOMS = Map.of(
Currency.RUB, List.of(5000, 1000, 500, 100, 50),
Currency.EUR, List.of(500, 100, 20));
private final Map<Currency, Map<Integer, Integer>> stock =
new EnumMap<>(Currency.class);
ATM(Map<Currency, Map<Integer, Integer>> initial) {
Objects.requireNonNull(initial);
for (var entry : initial.entrySet()) {
Currency currency = Objects.requireNonNull(entry.getKey());
Map<Integer, Integer> copy = new HashMap<>();
for (var bill : entry.getValue().entrySet()) {
Integer value = bill.getKey();
Integer count = bill.getValue();
if (value == null || !DENOMS.get(currency).contains(value)
|| count == null || count < 0) {
throw new IllegalArgumentException("Invalid stock");
}
copy.put(value, count);
}
stock.put(currency, copy);
}
}
synchronized Map<Integer, Integer> withdraw(int amount, Currency currency) {
Objects.requireNonNull(currency);
if (amount <= 0) throw new IllegalArgumentException("Invalid amount");
Map<Integer, Integer> bills = stock.getOrDefault(currency, Map.of());
Map<Integer, Integer> result = new LinkedHashMap<>();
int remaining = amount;
for (int denomination : DENOMS.get(currency)) {
int use = Math.min(remaining / denomination,
bills.getOrDefault(denomination, 0));
if (use > 0) {
result.put(denomination, use);
remaining -= denomination * use;
}
}
if (remaining != 0) throw new IllegalStateException("Cannot dispense");
result.forEach((value, count) -> bills.put(value, bills.get(value) - count));
return Map.copyOf(result);
}
}
При невозможности выдать сумму запас не меняется. synchronized защищает только один экземпляр внутри процесса; для нескольких процессов нужно общее транзакционное хранилище. Физическую выдачу и её сбои этот учебный пример не моделирует.