Skip to content

Latest commit

Β 

History

History
692 lines (553 loc) Β· 20.9 KB

File metadata and controls

692 lines (553 loc) Β· 20.9 KB

Test Day Guide - Rust Edition

πŸš€ Quick Reference: Test Structure

Time: 60 minutes total Questions: 2 coding problems Platform: Language: Rust

πŸ“Š Question Patterns (Based on 7+ Candidate Reports)

Question 1: CSV/Data Processing (Easier, 20-25 min)

  • Always: CSV parsing, string manipulation
  • Common: Price data (open/high/low/close), CSV ordering
  • Skills: Arrays, sorting, basic calculations, formatted output

Question 2: Complex Logic (Harder, 30-35 min)

Variant A: State Simulation (60% of reports)

  • Process commands line-by-line from CSV
  • State management over time (investment/payment journey)
  • Multiple conditional branches
  • Financial calculations with precision

Variant B: Complex Data Operations (40% of reports)

  • Heavy string manipulation with data structures
  • Extensive if-else logic (many branches)
  • Sorting and filtering operations
  • HashMap-intensive operations

🎯 Critical Success Factors

#1 Algorithm Efficiency (MOST IMPORTANT)

  • Time complexity is primary evaluation criterion
  • Working but inefficient = poor score
  • Think O(n) or O(n log n), avoid O(nΒ²)
  • Choose Rust for its performance advantages

#2 Time Management

  • Read BOTH questions first (2 minutes)
  • Check test cases immediately (3 minutes)
  • Identify harder question - consider tackling it first
  • Allocate: Q1 (20-25 min) + Q2 (30-35 min) + buffer (5-10 min)

#3 Common Mistakes to Avoid

  • ❌ Over-engineering solutions
  • ❌ Not checking test cases first
  • ❌ Poor time complexity
  • ❌ Missing submission steps

πŸ› οΈ Rust-Specific Tips

Efficient Data Structures

use std::collections::{HashMap, BTreeMap, VecDeque};

// HashMap for O(1) lookups
let mut price_map: HashMap<String, f64> = HashMap::new();

// BTreeMap for sorted iteration
// BTreeMap is a map (key-value store) that automatically keeps its keys in sorted order as you insert them.
// You do NOT need to sort it manuallyβ€”iteration over a BTreeMap always yields keys in ascending order.
// This is especially helpful when you need:
//   - Output sorted by key (e.g., alphabetically, numerically)
//   - Range or prefix queries on sorted data

// Example: Group CSV rows by "Brand" and print in alphabetical order
let mut brand_prices: BTreeMap<String, Vec<f64>> = BTreeMap::new();
for row in csv_rows {
    let brand = row[0].to_string();
    let price: f64 = row[1].parse().unwrap();
    brand_prices.entry(brand).or_default().push(price);
}
for (brand, prices) in brand_prices.iter() {
    println!("Brand: {}, Prices: {:?}", brand, prices); // Brands are printed in sorted order
}

// --- Common iteration and range functions (no manual sort needed) ---

// .iter(): Iterate (&key, &value) pairs in ascending (sorted) order
for (key, value) in btreemap.iter() {
    // Use sorted key-value pairs
}

// .range(): Iterate over a sorted subset of keys
for (k, v) in btreemap.range("AAPL".to_string()..="MSFT".to_string()) {
    // Only keys AAPL to MSFT (inclusive) in sorted order
}

// .values(): Iterate over values in ascending key order
for values in btreemap.values() {
    // values, sorted by their keys
}

// .keys(): Iterate over just the keys, in sorted order
for key in btreemap.keys() {
    // sorted keys
}

// .into_iter().rev(): Iterate in descending (reverse sorted) order
for (key, value) in btreemap.clone().into_iter().rev() {
    // reversed order
}

// NO need to call .sort(): insertion and iteration always use sorted keys!



let mut sorted_data: BTreeMap<String, Vec<f64>> = BTreeMap::new();

// Vec for dynamic arrays
let mut prices: Vec<f64> = Vec::new();

// VecDeque for efficient queue or stack behavior (push/pop at both ends in O(1))
// - Use for simulations that require FIFO or LIFO operations
// Example: processing a stream of events/commands line by line
let mut queue: VecDeque<&str> = VecDeque::new();
queue.push_back("first");  // enqueue
queue.push_front("urgent"); // push to front (LIFO)
let next = queue.pop_front(); // dequeue (FIFO) or pop_front for event processing

Quick Decision Guide: Which Data Structure?

Need This Operation Best Data Structure Why
Fast key-based lookups HashMap<K, V> O(1) average lookup time
Sorted key iteration BTreeMap<K, V> Automatically maintains sorted order
Random access by index Vec<T> O(1) access, O(n) insertion in middle
Queue operations (FIFO) VecDeque<T> O(1) push/pop at both ends
Stack operations (LIFO) Vec<T> Simple push/pop operations
Sorting arbitrary data Vec<T> + .sort() Dedicated sort methods available
Grouping/counting HashMap<K, Vec<T>> Efficient grouping by key
Ordered unique values BTreeSet<T> Sorted, no duplicates

Tip: 90% of problems use just Vec, HashMap, and BTreeMap. Start with Vec unless you need key-based access!

String Manipulation

// Parse CSV line
let fields: Vec<&str> = line.split(',').collect();

// Trim whitespace on both ends
let trimmed = s.trim();

// Trim whitespace only from left
let left_trimmed = s.trim_start();

// Trim whitespace only from right
let right_trimmed = s.trim_end();

// Joining and splitting
let combined = fields.join(","); // Vec<&str> to String
let fields: Vec<&str> = line.split(',').collect();

// Split on whitespace (most common for command parsing)
let parts: Vec<&str> = line.split_whitespace().collect(); // Removes empty fields
let parts: Vec<&str> = line.split(' ').collect(); // Keeps empty fields from multiple spaces

// Replace/substring
let no_dollar = price_str.replace("$", "");
let first3 = &word[0..3];

// Pattern matching
if value.starts_with("AAPL") {
    // Starts with...
}
if value.ends_with(".csv") {
    // Ends with...
}


// Parse numbers with error handling
if let Ok(price) = field.parse::<f64>() {
    // Use price
}

// Format with precision
format!("${:.2}", amount)

Command Parsing (Critical for Question 2)

// Most common pattern: parse commands with whitespace separation
fn parse_command(line: &str) -> Option<(&str, Vec<f64>)> {
    let parts: Vec<&str> = line.trim().split_whitespace().collect();
    if parts.is_empty() {
        return None;
    }

    let command = parts[0];
    let mut args = Vec::new();

    for arg_str in &parts[1..] {
        if let Ok(num) = arg_str.parse::<f64>() {
            args.push(num);
        }
    }

    Some((command, args))
}

// Example usage:
let line = "  SET_INTEREST 5.25  ";
if let Some((cmd, args)) = parse_command(line) {
    match cmd {
        "SET_INTEREST" => println!("Set interest rate: {:.2}%", args[0]),
        "INVEST" => println!("Invest amount: ${:.2}", args[0]),
        "TIME" => println!("Advance {} months", args[0] as i32),
        _ => println!("Unknown command: {}", cmd),
    }
}

// Simple command parsing without validation:
let parts: Vec<&str> = "SET_INTEREST 5.25".split_whitespace().collect();
let command = parts[0];  // "SET_INTEREST"
let rate: f64 = parts[1].parse().unwrap();  // 5.25

Iterator Methods (.map, .filter, .collect)

// .map() - Transform each element  
let dollar_prices = vec![12.99, 25.50, 8.75];
let prices_cents: Vec<i64> = dollar_prices.iter()
    .map(|&price| (price * 100.0).round() as i64)
    .collect();
// Result: [1299, 2550, 875] (exact cents, no floating point errors!)

// .filter() - Keep only elements that match condition
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<i32> = numbers.into_iter()
    .filter(|&x| x % 2 == 0)
    .collect();
// Result: [2, 4, 6]

// Chain them together (very powerful!)
let result: Vec<f64> = vec![10, 20, 30, 40, 50]
    .into_iter()
    .filter(|&x| x > 25)           // Keep numbers > 25
    .map(|x| x as f64 * 0.01)       // From cents to dollars
    .filter(|&x| x < 4.0)          // Keep amounts < $4.00
    .collect();
// Result: [3.0] (30 * 0.1 = 3.0)

// Real example: Process CSV data
let csv_lines = vec!["AAPL,150.25", "GOOG,2800.50", "INVALID", "MSFT,310.20"];
let valid_prices: Vec<f64> = csv_lines.iter()
    .filter_map(|line| {
        let parts: Vec<&str> = line.split(',').collect();
        if parts.len() == 2 {
            parts[1].parse::<f64>().ok()
        } else {
            None
        }
    })
    .collect();
// Result: [150.25, 2800.50, 310.20] (INVALID filtered out)

// .filter_map() - Filter + Map in one step (returns Option)
let strings = vec!["123", "abc", "456", "def", "789"];
let numbers: Vec<i32> = strings.iter()
    .filter_map(|s| s.parse::<i32>().ok())  // Try to parse, filter out failures
    .collect();
// Result: [123, 456, 789] (invalid strings filtered out)

// .enumerate() - Get index and value
let names = vec!["AAPL", "GOOG", "MSFT"];
for (index, name) in names.iter().enumerate() {
    println!("{}: {}", index, name);
}
// Output: 0: AAPL, 1: GOOG, 2: MSFT

// .zip() - Combine two iterators
let symbols = vec!["AAPL", "GOOG"];
let prices = vec![150.25, 2800.50];
let pairs: Vec<(&str, f64)> = symbols.iter()
    .zip(prices.iter())
    .map(|(&sym, &price)| (sym, price))
    .collect();
// Result: [("AAPL", 150.25), ("GOOG", 2800.50)]

Tip: Iterator methods work on all collections! Here's filter_map() with BTreeMap:

// BTreeMap + filter_map() example
let mut prices: BTreeMap<String, f64> = BTreeMap::new();
prices.insert("AAPL".to_string(), 150.25);
prices.insert("GOOG".to_string(), 2800.50);
prices.insert("INVALID".to_string(), -1.0);

// Filter invalid prices and transform (maintains sorted order!)
let valid_prices: Vec<f64> = prices.iter()
    .filter_map(|(symbol, &price)| {
        if price > 0.0 {
            Some(price)  // Keep valid prices
        } else {
            None         // Filter out invalid
        }
    })
    .collect();
// Result: [150.25, 2800.50] (AAPL first, then GOOG - sorted!)

Iterator methods are often more efficient and readable than manual loops. Use .filter_map() for parsing operations that might fail!

Time Manipulation (String-Based Only)

// has NO date/time libraries - only string manipulation!
// Convert "HH:MM" to minutes since midnight for comparison/arithmetic

fn time_to_minutes(time_str: &str) -> Option<i32> {
    let parts: Vec<&str> = time_str.split(':').collect();
    if parts.len() == 2 {
        if let (Ok(hours), Ok(minutes)) = (parts[0].parse::<i32>(), parts[1].parse::<i32>()) {
            if hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60 {
                return Some(hours * 60 + minutes);
            }
        }
    }
    None
}

fn minutes_to_time(minutes: i32) -> String {
    let hours = minutes / 60;
    let mins = minutes % 60;
    format!("{:02}:{:02}", hours, mins)
}

// Usage examples:
let time1 = "09:30";  // 9:30 AM
let time2 = "14:45";  // 2:45 PM

if let (Some(min1), Some(min2)) = (time_to_minutes(time1), time_to_minutes(time2)) {
    // Compare times
    if min1 < min2 {
        println!("{} is before {}", time1, time2);
    }

    // Calculate duration
    let duration = min2 - min1;  // in minutes
    println!("Duration: {} minutes", duration);

    // Add time
    let new_time = minutes_to_time(min1 + 120);  // Add 2 hours
    println!("2 hours later: {}", new_time);
}

// Sorting times
let times = vec!["14:30", "09:15", "11:45"];
let mut time_minutes: Vec<(i32, &str)> = times.iter()
    .filter_map(|&t| time_to_minutes(t).map(|m| (m, t)))
    .collect();

time_minutes.sort_by_key(|&(mins, _)| mins);

println!("Sorted times:");
for (_, time_str) in time_minutes {
    println!("{}", time_str);
}

Reality: No chrono, no DateTime - only string parsing and minute arithmetic!

Transforming Collections (.map() on collections)

use std::collections::BTreeSet;

// BTreeSet doesn't have .map() directly - use iterator + collect
let dollar_amounts: BTreeSet<f64> = [12.99, 25.50, 8.75, 12.99].into(); // Duplicates removed

// Convert dollars to cents (i64)
let cent_amounts: BTreeSet<i64> = dollar_amounts.into_iter()
    .map(|dollars| (dollars * 100.0).round() as i64)
    .collect();
// Result: {875, 1299, 2550} (sorted, unique cents)

// Mutable version: Start with dollars, transform to cents
let mut dollar_set: BTreeSet<f64> = BTreeSet::new();
dollar_set.insert(12.99);
dollar_set.insert(25.50);
dollar_set.insert(8.75);

// Transform to cents set
let cent_set: BTreeSet<i64> = dollar_set.into_iter()
    .map(|price| (price * 100.0).round() as i64)
    .collect();

println!("Cents: {:?}", cent_set); // {875, 1299, 2550}

// BTreeMap transformation (transform values, keep keys)
let mut price_map: BTreeMap<String, f64> = BTreeMap::new();
price_map.insert("AAPL".to_string(), 150.25);
price_map.insert("GOOG".to_string(), 2800.50);

// Transform values to cents, keep same keys
let cent_map: BTreeMap<String, i64> = price_map.into_iter()
    .map(|(symbol, price)| (symbol, (price * 100.0).round() as i64))
    .collect();

println!("Cent prices: {:?}", cent_map);
// {"AAPL": 15025, "GOOG": 280050} (sorted by symbol!)

// Alternative: Transform both keys and values
let symbol_to_cents: BTreeMap<String, i64> = price_map.into_iter()
    .map(|(symbol, price)| (symbol.to_uppercase(), (price * 100.0).round() as i64))
    .collect();

// Transform only keys (values stay the same)
let upper_case_map: BTreeMap<String, f64> = price_map.into_iter()
    .map(|(symbol, price)| (symbol.to_uppercase(), price))
    .collect();
// {"AAPL": 150.25, "GOOG": 2800.50}

// Filter + Transform: Keep only expensive stocks, convert to cents
let expensive_stocks: BTreeMap<String, i64> = price_map.into_iter()
    .filter(|(_, &price)| price > 1000.0)  // Keep only > $1000
    .map(|(symbol, price)| (symbol, (price * 100.0).round() as i64))
    .collect();
// {"GOOG": 280050} (only GOOG qualifies)

// Transform to different value type entirely
let stock_info: BTreeMap<String, String> = price_map.into_iter()
    .map(|(symbol, price)| {
        let cents = (price * 100.0).round() as i64;
        (symbol, format!("${:.2} ({} cents)", price, cents))
    })
    .collect();
// {"AAPL": "$150.25 (15025 cents)", "GOOG": "$2800.50 (280050 cents)"}

From dollars to cents

// For f64 to i64 conversion specifically
fn convert_prices_to_cents(prices: BTreeMap<String, f64>) -> BTreeMap<String, i64> {
    prices.into_iter()
        .map(|(symbol, price)| (symbol, (price * 100.0).round() as i64))
        .collect()
}

CSV Processing Pattern

fn process_csv(csv_data: &str) -> String {
    let lines: Vec<&str> = csv_data.trim().split('\n').collect();
    let mut result = Vec::new();

    for line in &lines[1..] {  // Skip header
        let fields: Vec<&str> = line.split(',').collect();
        // Process fields...
    }

    result.join("\n")
}

Reading CSV Files (Not needed for )

use std::fs;
use std::io::{self, BufRead};

// Read entire CSV file into string (simple approach)
fn read_csv_file_simple(filename: &str) -> Result<String, io::Error> {
    fs::read_to_string(filename)
}

// Read CSV line by line (memory efficient for large files)
fn read_csv_file_efficient(filename: &str) -> Result<Vec<String>, io::Error> {
    let file = fs::File::open(filename)?;
    let reader = io::BufReader::new(file);
    let mut lines = Vec::new();

    for line in reader.lines() {
        lines.push(line?);
    }

    Ok(lines)
}

// Process CSV file directly
fn process_csv_file(filename: &str) -> Result<String, io::Error> {
    let content = fs::read_to_string(filename)?;
    // Now process like normal CSV string
    let lines: Vec<&str> = content.trim().split('\n').collect();
    let mut result = Vec::new();

    for line in &lines[1..] {  // Skip header
        let fields: Vec<&str> = line.split(',').collect();
        // Process fields...
        result.push(fields.join(",")); // Example
    }

    Ok(result.join("\n"))
}

Note: provides CSV data as string parameters, so you won't need file I/O. This is just for reference if you encounter file-based problems elsewhere.

πŸ“ Test Day Strategy

Phase 1: Reading (5 minutes)

  1. Read Question 1 completely
  2. Read Question 2 completely
  3. Note key requirements and constraints
  4. Assess difficulty - which seems harder?

Phase 2: Planning (5 minutes)

  1. Check test cases for both questions
  2. Understand exact input/output format
  3. Think algorithm efficiency first
  4. Decide question order (consider harder first)

Phase 3: Implementation

  1. Start with chosen question
  2. Write clean, readable Rust code
  3. Test frequently as you code
  4. Focus on correctness first, then optimize

Phase 4: Submission (5 minutes)

  1. Test edge cases
  2. Review code for clarity
  3. Click "Submit test"
  4. Click "Finish test" (CRITICAL - don't miss this!)

🚨 Red Flags During Test

Time Pressure

  • Don't panic - focus on simpler question if stuck
  • Check test cases - they guide the solution
  • Simple efficient solution > perfect incomplete code

Getting Stuck

  • Re-read the problem carefully
  • Look at test cases again
  • Break problem into smaller steps
  • Consider edge cases

Algorithm Issues

  • Brute force may pass small tests but score poorly
  • Think: Can I do this in O(n) instead of O(nΒ²)?
  • HashMap lookups are O(1), sorting is O(n log n)

πŸ’‘ Key Algorithm Patterns

CSV Processing

  • Parse lines, split by commas
  • Handle headers separately
  • Use HashMap for grouping by key
  • BTreeMap for sorted output

State Simulation

  • Define clear state struct
  • Process commands in loop
  • Update state based on command type
  • Validate state transitions

Financial Calculations

  • Use f64 for precision
  • Round to 2 decimal places when required
  • Handle compound interest carefully
  • Watch for floating-point precision issues

Compound Interest Formulas

Simple Interest (One-time application):

// Interest rate: 5.25% on $1000
let principal = 1000.0;
let rate_percent = 5.25;
let multiplier = 1.0 + (rate_percent / 100.0); // 1.0525
let result = principal * multiplier; // $1052.50

Compound Interest (Multiple applications):

// Command sequence: ["DEPOSIT 1000.00", "INTEREST 5.25", "COMPOUND"]
let balance = 1000.0;
let rate = 5.25;

// First INTEREST command
balance = balance * (1.0 + rate/100.0); // $1052.50

// Later COMPOUND command (applies same rate again)
balance = balance * (1.0 + rate/100.0); // $1107.56

// Multiple compounding periods
for _ in 0..periods {
    balance = balance * (1.0 + rate/100.0);
}

Common Patterns:

  • INTEREST rate β†’ Sets rate AND applies interest immediately
  • COMPOUND β†’ Applies previously set interest rate again
  • Store interest_rate as instance variable between commands
  • Always use .round() for financial precision

f64 Money Pitfalls & Solutions

❌ Common f64 Problems:

// PITFALL: Floating point can't represent 0.1 exactly
let price = 0.1 + 0.2; // 0.30000000000000004, not 0.3!

// PITFALL: Accumulating errors in loops
let mut total = 0.0;
for _ in 0..10 {
    total += 0.1; // total might be 0.9999999999999999, not 1.0!
}

// PITFALL: Comparison issues
if total == 1.0 { /* This might never be true! */ }

βœ… Solutions for :

  1. Use Integer Arithmetic for Money (See financial_calculations.rs):
// Store money as cents (i32) or with fixed precision
let price_cents = 299; // $2.99
let total_cents = price_cents * 3; // 897 cents = $8.97

// For display
println!("${:.2}", total_cents as f64 / 100.0);
  1. Round at Critical Points:
let result = (calculation * 100.0).round() / 100.0; // Round to 2 decimals
  1. Avoid Direct Equality:
// Instead of: if amount == 0.0
if (amount * 100.0).round() == 0.0 { /* Check for zero */ }

// Or use epsilon comparison
const EPSILON: f64 = 0.001;
if (amount - target).abs() < EPSILON { /* Close enough */ }
  1. Use PreciseDecimal Pattern (from practice code):
const PRECISION: i64 = 100; // 2 decimal places

#[derive(Clone)]
struct Money {
    cents: i64, // Store as integer
}

impl Money {
    fn new(amount: f64) -> Self {
        Money { cents: (amount * PRECISION as f64).round() as i64 }
    }

    fn to_f64(&self) -> f64 {
        self.cents as f64 / PRECISION as f64
    }

    fn add(&self, other: &Money) -> Money {
        Money { cents: self.cents + other.cents }
    }
}

Tip: For financial problems, expect exact 2-decimal precision. Use rounding and avoid direct f64 equality checks!

πŸ† Success Checklist

Pre-Test

  • Practice all pattern types
  • Know Rust standard library well
  • Can write efficient algorithms quickly
  • Comfortable with time pressure

During Test

  • Read both questions first
  • Check test cases immediately
  • Focus on algorithm efficiency
  • Write clean, working code
  • Test frequently

Post-Test

  • Click "Submit test"
  • Click "Finish test" (CRITICAL!)
  • Don't forget either step

🎯 Final Motivation

Remember: Algorithm efficiency + clean code wins. The problems themselves aren't extremely hard - doing them optimally is the real challenge. You have the Rust skills and practice - focus on efficiency and you'll succeed!

Good luck! You've got this. πŸš€