Skip to content

Latest commit

 

History

History
170 lines (111 loc) · 6.5 KB

File metadata and controls

170 lines (111 loc) · 6.5 KB

Bank Account System 🏦🧾

Problem statement 📝

Design a Bank class that manages n bank accounts and supports three operations:

  • transfer(account1, account2, money) — if both accounts exist and account1 has at least money, move money from account1 to account2, return true; otherwise return false.
  • deposit(account, money) — if account exists, add money to the account and return true; otherwise return false.
  • withdraw(account, money) — if account exists and has at least money, subtract money and return true; otherwise return false.

Accounts are 1-indexed (account numbers 1..n). Balances are large integers (use long long).

Intuition ✨

This is a straightforward stateful data-structure problem: store balances in a container (vector) indexed by account id minus one. Each operation is just input validation + small arithmetic on two entries. Because n can be large but operations are O(1), the overall performance is excellent for typical usage.

Key things to watch for:

  • Indexing: user-facing accounts are 1-indexed while vector is 0-indexed.
  • Validation: must check account bounds and sufficient funds for withdraw/transfer.
  • Integer overflow: adding money might overflow long long if inputs are adversarial; decide whether to guard or rely on constraints.
  • Robustness: reject negative money (if domain forbids it). Protect against misuse.
  • Thread-safety: class is not thread-safe; add locking if concurrency is required.

Brute force vs Optimal

  • Brute force doesn't apply here — operations are constant-time by definition.
  • Optimal is simply O(1) per operation using direct index access. That's what the provided design already does.

code review ✅

class Bank {
public:
    int n;
    vector<long long> bal;

    bool validAc(int acc) {
        if (acc > 0 && acc <= n)
            return true;
        else return false;
    }

    Bank(vector<long long>& balance) {
        bal = balance;
        n = balance.size();
    }
    
    bool transfer(int account1, int account2, long long money) {
        if (!validAc(account1) || !validAc(account2) || bal[account1-1] < money)
            return false;
        
        bal[account1-1] -= money;
        bal[account2-1] += money;
        return true;
    }
    
    bool deposit(int account, long long money) {
        if (!validAc(account))
            return false;
        
        bal[account-1] += money;
        return true;
    }
    
    bool withdraw(int account, long long money) {
        if (!validAc(account) || bal[account-1] < money)
            return false;

        bal[account-1] -= money;
        return true;
    }
};

Complexity analysis 📊

  • Time (per operation): O(1) — constant-time checks and index updates.
  • Space: O(n) to store balances.
  • Initialization: O(n) to copy the balances into the vector (or O(1) if you move in the constructor).

This is optimal for the required functionality.

Representative test cases 🧪

Assume accounts [10, 100, 20, 50, 30] (accounts 1..5).

  1. Valid transfer

    • transfer(1, 2, 5) → true Balances → [5, 105, 20, 50, 30]
  2. Insufficient funds

    • transfer(1, 3, 10) → false (account1 has only 5 now)
  3. Withdraw valid

    • withdraw(2, 100) → true → balances [5, 5, 20, 50, 30]
  4. Withdraw insufficient

    • withdraw(3, 30) → false
  5. Deposit

    • deposit(4, 1000) → true → balances [5, 5, 20, 1050, 30]
  6. Invalid account

    • transfer(0, 2, 1) or deposit(6, 10) → false
  7. Negative money

    • deposit(1, -10) → false (rejected)
  8. Overflow guard (if enabled)

    • set account 5 to near LLONG_MAX, trying deposit(5, 1000) should return false or be handled.

Edge-cases & gotchas ⚠️

  • 1-indexing — remember to subtract 1 for vector access.
  • Negative amounts — invalid input usually; decide policy and enforce.
  • Overflow — if inputs may be near LLONG_MAX, consider safe add or use __int128 internally.
  • Concurrency — simultaneous operations on accounts can lead to race conditions. Use locks if necessary.
  • Large sizesn can be large; ensure memory fits.
  • Signed/unsigned mixing — casting to size_t helps avoid warnings, but be consistent.

Variations & extensions 🔁

  • Thread-safe Bank: add std::mutex per account or one global mutex. Per-account locks improve concurrency for operations on different accounts.
  • Transaction logging: store a history of operations for auditing and rollback.
  • Atomic transfers: ensure transfer is atomic (deduct+add) under concurrency using locks or atomic math for simpler cases.
  • Support currency & fees: add decimals with long long cents or use arbitrary precision.
  • Fraud detection rules: block transfers above a threshold or to suspicious accounts.

Tips & best practices ✨

  • Use const vector<long long>& in constructor to avoid extra copies; if caller won't need balance afterwards, provide an rvalue overload (Bank(vector<long long>&&)) to move.
  • Keep validation centralized in one helper (like validAc) to avoid mistakes.
  • Prefer using size_t for index checks (acc >= 1 && acc <= n), but be careful with signed int parameters — cast explicitly.
  • Document the indexing scheme (1-based) and behavior with negative/oversized amounts.
  • Write unit tests to validate boundary conditions: highest/lowest balances, invalid accounts, negative money, concurrency if applicable.

Frequently Asked Questions (FAQs) ❓

Q: Should I support negative money values?

A: Typically no. Banking operations deal with non-negative amounts. Reject negatives or interpret them explicitly (e.g., deposit(-x) == withdraw(x)) only if problem states so.

Q: Do I need to worry about overflow?

A: If input constraints guarantee balances and money fit in 64-bit signed (long long), you can skip checks. For robustness use overflow guards.

Q: How to make this thread-safe?

A: Add std::mutex for each account and lock both accounts in transfer (use consistent locking order to avoid deadlock), or use std::shared_mutex as appropriate.

Q: Is this implementation optimal?

A: Yes — all operations are O(1). The bottleneck is memory O(n), unavoidable if you must store every balance.

Q: Should balances be signed?

A: long long is fine; if negative balance allowed (overdraft), allow negative values; otherwise reject operations that would produce negative balance.