← Computer Programming I

Problem 1: The Price Tag Cleaner

You are scraping data from an e-commerce website. The price data is messy: it comes as a list of strings. Some have currency symbols ("$10.50"), some represent free items ("Free"), and some are corrupted ("Error" or "NaN").

Write a function sum_valid_prices(price_list) that calculates the total cost.

  1. Iterate through the list.
  2. For each item:
    • If the string is exactly "Free" (case-insensitive), treat the price as 0.0.
    • If the string contains a "$" sign, remove it before attempting conversion.
    • Try to convert the cleaned string to a float.
    • Except: If conversion fails (e.g., “Error”), print "Skipping invalid price: [value]" and continue.
  3. Return the sum of all valid prices.

Test Cases & Expected Output:

raw_prices = ["$12.50", "Free", "error_404", "$5.00", "2.50", "N/A"]
total = sum_valid_prices(raw_prices)
print(f"Total: ${total}")

Expected Output:

Skipping invalid price: error_404
Skipping invalid price: N/A
Total: $20.0

Problem 2: The Server Config Parser

You are parsing a configuration file for a game server. Each setting is a string in the format "setting_name:value" (e.g., "volume:80"). You receive a list of these strings.

Write a function parse_settings(config_lines) that returns a dictionary of valid settings.

  1. Iterate through the lines.
  2. Inside a try block:
    • Split the string by ":".
    • Validate Structure: If the split result doesn’t have exactly 2 elements, raise an IndexError.
    • Validate Type: Convert the value (the second part) to an integer.
    • Validate Logic: The value must be between 0 and 100. If not, raise a ValueError with the message “Out of range”.
  3. Except:
    • Catch IndexError: Print "Format error in: [line]".
    • Catch ValueError: Print "Invalid value in: [line] ([reason])".
  4. Return the dictionary of valid settings.

Test Cases:

configs = [
    "volume:80",          # Valid
    "brightness:120",     # Invalid Range
    "difficulty:hard",    # Invalid Type
    "mute",               # Invalid Format (no colon)
    "contrast:50"         # Valid
]
settings = parse_settings(configs)
print(f"Loaded Settings: {settings}")

Expected Output:

Invalid value in: brightness: 120 (Out of range)
Invalid value in: difficulty: hard (invalid literal for int() with base 10: 'hard')
Format error in: mute
Loaded Settings: {'volume': 80, 'contrast': 50}

Problem 3: The Safe Shopping Cart

You are building a checkout system.

  • inventory: A dictionary mapping item names to prices. Warning: Some items in the inventory have corrupted prices (set to None or negative numbers).
  • cart: A list of item names a user wants to buy.

Write a function checkout(inventory, cart) that:

  1. Iterates through the cart.
  2. Try:
    • Look up the item in inventory.
    • If the price is None, raise a TypeError (“Price missing”).
    • If the price is less than 0, raise a ValueError (“Invalid price”).
    • Add the price to the total_cost.
  3. Except:
    • Handle KeyError (Item not in store): Print "Item not found: [name]".
    • Handle TypeError / ValueError (Bad data in store): Print "Data error for [name]: [reason]".
  4. Count how many items failed to process.
  5. Return a tuple: (total_cost, failed_count).

Test Cases:

store_db = {
    "Apple": 0.50, 
    "Banana": 0.30, 
    "GhostItem": None,     # Corrupt data
    "GlitchItem": -5.00    # Corrupt data
}
my_cart = ["Apple", "Mango", "GhostItem", "Banana", "GlitchItem"]

cost, errors = checkout(store_db, my_cart)
print(f"Total: ${cost}, Errors: {errors}")

Expected Output:

Item 'Mango' not found
Data error for 'GhostItem': Price missing in database
Data error for 'GlitchItem': Negative price detected
Total: $0.8, Errors: 3

Problem 4: Bank Account with Overdraft Protection

Write a function withdraw_funds(account, amount) to manage withdrawals with strict overdraft rules.

  • account: A dictionary {'balance': float, 'limit': float}. (Limit is the max negative balance allowed, e.g., -100).
  • amount: The amount to withdraw.

Logic & Exception Rules:

  1. Input Check: If amount is not a number or is negative, raise ValueError (“Invalid amount”).
  2. Calculation: Calculate new_balance = current_balance - amount.
  3. Validation:
    • If new_balance is positive or zero: The transaction is safe.
    • If new_balance is negative but greater than or equal to the overdraft limit: Raise a UserWarning (Note: standard Python exception) with message “Overdraft used”. The transaction should still happen!
    • If new_balance is less than the overdraft limit: Raise ValueError with message “Overdraft limit exceeded”. The transaction should NOT happen.

Task: Write a loop that attempts 3 transactions. Use try/except to:

  • Catch UserWarning: Print the warning but confirm the transaction happened.
  • Catch ValueError: Print the error and confirm the transaction failed.
  • Catch success (via else): Print “Transaction successful”.
  • Always print the remaining balance in finally.

Test Cases:

my_account = {'balance': 50.0, 'limit': -100.0}
attempts = [40, 60, 200] 
# 1. 50 - 40 = 10 (Safe)
# 2. 10 - 60 = -50 (Warning, but works)
# 3. -50 - 200 = -250 (Fail)

Expected Output:

Attempting to withdraw $40...
Transaction successful.
Current Balance: 10.0

Attempting to withdraw $60...
Alert: Overdraft used. Balance is -50.0
Transaction completed with warning.
Current Balance: -50.0

Attempting to withdraw $200...
Transaction Failed: Overdraft limit exceeded
Current Balance: -50.0

Problem 5: The "Atomic" Bank Transfer (Transaction Rollback)

In financial software, a transaction is considered “Atomic” if it either completely succeeds or completely fails. You cannot withdraw money from Person A, fail to deposit it to Person B, and leave the money in limbo. If the second step fails, you must “undo” (rollback) the first step.

Write a function perform_atomic_transfer(accounts, sender, receiver, amount) that manages this process.

The Data: accounts is a dictionary: {"UserA": 100.0, "UserB": 50.0}.

The Rules:

  1. Validation Phase:
    • If sender doesn’t exist, raise KeyError (“Sender not found”).
    • If sender has insufficient funds, raise ValueError (“Insufficient funds”).
    • Do not check the receiver yet.
  2. Withdrawal Phase:
    • Deduct amount from the sender’s balance.
    • Print "Withdrew [amount] from [sender]".
  3. Deposit Phase (The Risk Zone):
    • Try to add the money to the receiver.
    • If receiver does not exist in the dictionary, this operation will fail (you can simulate this by trying to access accounts[receiver]).
    • If it fails (KeyError), you must Catch the error, and perform a Rollback:
      • Add the amount back to the sender.
      • Print "Error: Receiver not found. Refunding [sender]...".
      • Re-raise the exception (using raise) so the main program knows the transfer failed.
  4. If everything succeeds, return True.

Test Script: Create a main block with a try/except wrapper to test:

  1. A successful transfer (UserA -> UserB).
  2. A transfer to a non-existent user (UserA -> UserX). Verify UserA’s balance is restored.
  3. A transfer with insufficient funds.

Test Cases::

bank_db = {"Alice": 100.0, "Bob": 50.0}

Expected Output:

--- Test 1: Successful Transfer ---
-> Withdrew $30.0 from Alice.
-> Deposited $30.0 to Bob.
Transfer Complete.
Current State: {'Alice': 70.0, 'Bob': 80.0}

--- Test 2: Failed Transfer (Bad Receiver) ---
-> Withdrew $20.0 from Alice.
   [!] Transaction failed: "Receiver 'Charlie' not found."
   [!] Rolling back: Refunding $20.0 to Alice.
Main Error Handler: "Receiver 'Charlie' not found."
Current State: {'Alice': 70.0, 'Bob': 80.0}

--- Test 3: Insufficient Funds ---
Main Error Handler: Sender 'Bob' has insufficient funds.

Problem 6: The Smart Home Command Processor

You are building the control logic for a Smart Home Hub. Devices are controlled by sending text command strings like "LIGHT_LIVING:BRIGHTNESS:80". You must parse these strings, validate every part of them, and update the device state.

Data Structure:

devices = {
    "LIGHT_LIVING": {"type": "dimmer", "value": 50, "range": (0, 100)},
    "THERMOSTAT":   {"type": "temp",   "value": 22, "range": (15, 30)},
    "DOOR_LOCK":    {"type": "binary", "value": 0,  "range": (0, 1)} # 0=Locked, 1=Unlocked
}

Task: Write a function process_commands(device_db, command_list) that iterates through a list of command strings. The command format is strict: "DEVICE_ID:ACTION:VALUE".

Inside the loop, implement a robust try/except chain to handle:

  1. Format Errors: If the string doesn’t have exactly 3 parts, raise ValueError (“Invalid command format”).
  2. Unknown Device: If the ID isn’t in the DB, raise KeyError (“Unknown device”).
  3. Unknown Action: The only allowed action is "SET". If anything else, raise ValueError (“Unsupported action”).
  4. Value Type: The value must be an integer. If not, catch the conversion error (raise ValueError).
  5. Range Validation: Check the device’s specific min/max range tuple. If outside, raise ValueError (“Value out of range”).

If an error occurs, print "Skipping command '[cmd]': [Reason]" and continue to the next command. If successful, update the value in the dictionary and print "Updated [Device] to [Value]".

Test Input:

cmds = [
    "LIGHT_LIVING:SET:100",    # Valid
    "LIGHT_LIVING:SET:200",    # Out of range
    "THERMOSTAT:COOL:20",      # Invalid Action
    "GARAGE_DOOR:SET:1",       # Unknown Device
    "DOOR_LOCK:SET:OPEN",      # Invalid Value Type
    "bad_format_command"       # Bad Format
]

Expected Output:

--- Processing Smart Home Commands ---
Success: Updated LIGHT_LIVING to 100
Skipping command 'LIGHT_LIVING:SET:200': Value 200 outside allowed range 0-100
Skipping command 'THERMOSTAT:COOL:20': Unsupported action 'COOL'
Skipping command 'GARAGE_DOOR:SET:1': "Unknown device ID 'GARAGE_DOOR'"
Skipping command 'DOOR_LOCK:SET:OPEN': Value 'OPEN' must be an integer
Skipping command 'bad_format_command': Invalid command format (expected ID:ACTION:VALUE)

Final State: {
    'LIGHT_LIVING': {'type': 'dimmer', 'value': 100, 'range': (0, 100)}, 
    'THERMOSTAT': {'type': 'temp', 'value': 22, 'range': (15, 30)}, 
    'DOOR_LOCK': {'type': 'binary', 'value': 0, 'range': (0, 1)}}