* Банкомат.

Для заданных номиналов можно идти от крупных купюр к мелким, сначала рассчитать выдачу, а затем списать остатки. Операция должна быть атомарной, валюты — храниться раздельно.

Короткий ответ

Что ответить на собеседовании

Для заданных номиналов можно идти от крупных купюр к мелким, сначала рассчитать выдачу, а затем списать остатки. Операция должна быть атомарной, валюты — храниться раздельно.

Подробный разбор

Ответ с пояснениями

Условие

/**

  • Банкомат.
  • Инициализируется набором купюр и валюты (например, 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 защищает только один экземпляр внутри процесса; для нескольких процессов нужно общее транзакционное хранилище. Физическую выдачу и её сбои этот учебный пример не моделирует.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее