learn.sol
Week 2 • Day 5 Collections Strings

Rust Collections and String Handling (Vectors, HashMap, BTreeMap)

Learn Rust collections and string handling with vectors, hash maps, BTreeMap, sets, and efficient string operations to manage data in Solana-style apps.

Today's Mission: Data Organization Masters

Learn to efficiently store, organize, and manipulate data using Rust's collection types. Essential for managing accounts, transactions, and program state in Solana applications.

📊 Rust Collections Overview

Rust provides several powerful collection types for different use cases:


🚀 Vectors: Dynamic Arrays

Vectors are the most commonly used collection type in Rust.

Creating and Manipulating Vectors

fn vector_basics() {
    // Creating vectors
    let mut numbers = Vec::new();
    let mut fruits = vec!["apple", "banana", "orange"];
    let zeros = vec![0; 5]; // [0, 0, 0, 0, 0]
    
    // Adding elements
    numbers.push(1);
    numbers.push(2);
    numbers.push(3);
    
    fruits.push("grape");
    
    println!("Numbers: {:?}", numbers);
    println!("Fruits: {:?}", fruits);
    
    // Accessing elements
    let first = &numbers[0]; // Panics if index doesn't exist
    let second = numbers.get(1); // Returns Option<&T>
    
    match second {
        Some(value) => println!("Second number: {}", value),
        None => println!("No second number"),
    }
    
    // Iterating over vectors
    println!("Iterating over numbers:");
    for (index, number) in numbers.iter().enumerate() {
        println!("Index {}: {}", index, number);
    }
    
    // Mutable iteration
    for number in &mut numbers {
        *number *= 2;
    }
    println!("Doubled numbers: {:?}", numbers);
}

Advanced Vector Operations

#[derive(Debug, Clone)]
struct Transaction {
    id: String,
    amount: u64,
    timestamp: u64,
}

fn advanced_vector_operations() {
    let mut transactions = vec![
        Transaction { id: "tx1".to_string(), amount: 100, timestamp: 1000 },
        Transaction { id: "tx2".to_string(), amount: 250, timestamp: 1001 },
        Transaction { id: "tx3".to_string(), amount: 75, timestamp: 1002 },
        Transaction { id: "tx4".to_string(), amount: 300, timestamp: 1003 },
    ];
    
    // Filtering
    let large_transactions: Vec<&Transaction> = transactions
        .iter()
        .filter(|tx| tx.amount > 150)
        .collect();
    
    println!("Large transactions: {:#?}", large_transactions);
    
    // Mapping
    let transaction_ids: Vec<String> = transactions
        .iter()
        .map(|tx| tx.id.clone())
        .collect();
    
    println!("Transaction IDs: {:?}", transaction_ids);
    
    // Finding elements
    let tx_by_id = transactions
        .iter()
        .find(|tx| tx.id == "tx2");
    
    if let Some(tx) = tx_by_id {
        println!("Found transaction: {:?}", tx);
    }
    
    // Sorting
    transactions.sort_by(|a, b| a.amount.cmp(&b.amount));
    println!("Sorted by amount: {:#?}", transactions);
    
    // Grouping (using itertools would be easier, but let's do it manually)
    let mut high_value = Vec::new();
    let mut low_value = Vec::new();
    
    for tx in transactions {
        if tx.amount > 200 {
            high_value.push(tx);
        } else {
            low_value.push(tx);
        }
    }
    
    println!("High value transactions: {:?}", high_value.len());
    println!("Low value transactions: {:?}", low_value.len());
}

Vector Performance Tips

fn vector_performance_tips() {
    // Pre-allocate capacity when size is known
    let mut large_vec = Vec::with_capacity(1000);
    
    // This is more efficient than letting the vector grow dynamically
    for i in 0..1000 {
        large_vec.push(i);
    }
    
    println!("Vector capacity: {}, length: {}", large_vec.capacity(), large_vec.len());
    
    // Using extend for multiple elements
    let mut numbers = vec![1, 2, 3];
    numbers.extend([4, 5, 6]);
    println!("Extended vector: {:?}", numbers);
    
    // Draining elements
    let drained: Vec<i32> = numbers.drain(2..5).collect();
    println!("Drained elements: {:?}", drained);
    println!("Remaining numbers: {:?}", numbers);
    
    // Removing elements efficiently
    let mut items = vec!["a", "b", "c", "d", "e"];
    
    // Remove by index (preserves order)
    let removed = items.remove(2);
    println!("Removed '{}', remaining: {:?}", removed, items);
    
    // Swap remove (doesn't preserve order, but O(1))
    items.swap_remove(1);
    println!("After swap_remove: {:?}", items);
}

🗺️ HashMap: Key-Value Storage

HashMap provides fast key-value lookups with O(1) average performance.

Basic HashMap Operations

use std::collections::HashMap;

fn hashmap_basics() {
    // Creating HashMaps
    let mut scores = HashMap::new();
    let mut account_balances: HashMap<String, u64> = HashMap::new();
    
    // Inserting values
    scores.insert("Alice", 95);
    scores.insert("Bob", 87);
    scores.insert("Charlie", 92);
    
    account_balances.insert("wallet1".to_string(), 1000000);
    account_balances.insert("wallet2".to_string(), 500000);
    
    // Accessing values
    let alice_score = scores.get("Alice");
    match alice_score {
        Some(score) => println!("Alice's score: {}", score),
        None => println!("Alice not found"),
    }
    
    // Using unwrap_or for defaults
    let david_score = scores.get("David").unwrap_or(&0);
    println!("David's score: {}", david_score);
    
    // Iterating over HashMap
    println!("All scores:");
    for (name, score) in &scores {
        println!("{}: {}", name, score);
    }
    
    // Checking if key exists
    if scores.contains_key("Alice") {
        println!("Alice is in the scores map");
    }
    
    // Removing entries
    let removed_score = scores.remove("Charlie");
    if let Some(score) = removed_score {
        println!("Removed Charlie with score: {}", score);
    }
}

Advanced HashMap Patterns

use std::collections::HashMap;

#[derive(Debug, Clone)]
struct AccountInfo {
    balance: u64,
    last_transaction: u64,
    transaction_count: u32,
}

impl Default for AccountInfo {
    fn default() -> Self {
        Self {
            balance: 0,
            last_transaction: 0,
            transaction_count: 0,
        }
    }
}

fn advanced_hashmap_patterns() {
    let mut accounts: HashMap<String, AccountInfo> = HashMap::new();
    
    // Using entry API for complex insertions
    let account1 = "wallet_abc123".to_string();
    
    // Insert if not present, otherwise update
    accounts.entry(account1.clone())
        .and_modify(|info| {
            info.balance += 1000;
            info.transaction_count += 1;
        })
        .or_insert(AccountInfo {
            balance: 1000,
            last_transaction: 12345,
            transaction_count: 1,
        });
    
    // Counting occurrences pattern
    let transactions = vec!["wallet1", "wallet2", "wallet1", "wallet3", "wallet1"];
    let mut transaction_counts: HashMap<&str, u32> = HashMap::new();
    
    for wallet in transactions {
        *transaction_counts.entry(wallet).or_insert(0) += 1;
    }
    
    println!("Transaction counts: {:#?}", transaction_counts);
    
    // Grouping data
    #[derive(Debug)]
    struct Transfer {
        from: String,
        to: String,
        amount: u64,
    }
    
    let transfers = vec![
        Transfer { from: "A".to_string(), to: "B".to_string(), amount: 100 },
        Transfer { from: "A".to_string(), to: "C".to_string(), amount: 200 },
        Transfer { from: "B".to_string(), to: "C".to_string(), amount: 150 },
        Transfer { from: "A".to_string(), to: "D".to_string(), amount: 300 },
    ];
    
    // Group transfers by sender
    let mut transfers_by_sender: HashMap<String, Vec<&Transfer>> = HashMap::new();
    
    for transfer in &transfers {
        transfers_by_sender
            .entry(transfer.from.clone())
            .or_insert_with(Vec::new)
            .push(transfer);
    }
    
    println!("Transfers by sender: {:#?}", transfers_by_sender);
    
    // Calculate total sent by each account
    let mut total_sent: HashMap<String, u64> = HashMap::new();
    
    for transfer in &transfers {
        *total_sent.entry(transfer.from.clone()).or_insert(0) += transfer.amount;
    }
    
    println!("Total sent by account: {:#?}", total_sent);
}

HashMap vs BTreeMap

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

fn map_comparison() {
    // HashMap: Fast, unordered
    let mut hash_map = HashMap::new();
    hash_map.insert("zebra", 26);
    hash_map.insert("apple", 1);
    hash_map.insert("banana", 2);
    
    println!("HashMap iteration (random order):");
    for (key, value) in &hash_map {
        println!("{}: {}", key, value);
    }
    
    // BTreeMap: Slower insertion/lookup, but ordered
    let mut btree_map = BTreeMap::new();
    btree_map.insert("zebra", 26);
    btree_map.insert("apple", 1);
    btree_map.insert("banana", 2);
    
    println!("\nBTreeMap iteration (sorted order):");
    for (key, value) in &btree_map {
        println!("{}: {}", key, value);
    }
    
    // BTreeMap supports range operations
    println!("\nEntries from 'a' to 'c':");
    for (key, value) in btree_map.range("a".."d") {
        println!("{}: {}", key, value);
    }
}

🔤 String Handling in Rust

Rust has two main string types: String (owned) and &str (borrowed).

String Basics

fn string_basics() {
    // String literals (&str)
    let greeting = "Hello, Solana!";
    let multiline = "This is a
multiline string";
    
    // Creating String (owned)
    let mut owned_string = String::new();
    let from_literal = String::from("Hello");
    let from_str = "World".to_string();
    
    // String operations
    owned_string.push_str("Hello");
    owned_string.push(' ');
    owned_string.push_str("Rust");
    
    println!("Owned string: {}", owned_string);
    
    // String concatenation
    let first = String::from("Hello");
    let second = String::from("World");
    
    // Method 1: Using + (moves first string)
    let combined = first + " " + &second;
    println!("Combined: {}", combined);
    // Note: `first` is no longer available here
    
    // Method 2: Using format! macro
    let greeting = format!("{} {}!", "Hello", "Solana");
    println!("Formatted: {}", greeting);
    
    // Method 3: Using push_str (mutating)
    let mut message = String::from("Building on");
    message.push_str(" Solana");
    message.push('!');
    println!("Message: {}", message);
}

String Slicing and Indexing

fn string_slicing() {
    let text = "Hello, 世界"; // Mixed ASCII and Unicode
    
    // Length in bytes vs characters
    println!("Length in bytes: {}", text.len());
    println!("Length in chars: {}", text.chars().count());
    
    // Slicing (be careful with Unicode!)
    let hello = &text[0..5]; // This works because "Hello" is ASCII
    println!("Slice: {}", hello);
    
    // Safe slicing with get
    let safe_slice = text.get(0..5);
    match safe_slice {
        Some(s) => println!("Safe slice: {}", s),
        None => println!("Invalid slice range"),
    }
    
    // Iterating over characters
    println!("Characters:");
    for (i, c) in text.chars().enumerate() {
        println!("  {}: {}", i, c);
    }
    
    // Iterating over bytes
    println!("Bytes:");
    for (i, b) in text.bytes().enumerate() {
        println!("  {}: {}", i, b);
    }
    
    // Working with lines
    let multiline = "Line 1\nLine 2\nLine 3";
    for (i, line) in multiline.lines().enumerate() {
        println!("Line {}: {}", i + 1, line);
    }
}

String Parsing and Validation

use std::str::FromStr;

#[derive(Debug)]
struct WalletAddress(String);

impl FromStr for WalletAddress {
    type Err = String;
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Simple validation for demo purposes
        if s.len() != 44 {
            return Err("Wallet address must be 44 characters long".to_string());
        }
        
        if !s.chars().all(|c| c.is_alphanumeric()) {
            return Err("Wallet address must be alphanumeric".to_string());
        }
        
        Ok(WalletAddress(s.to_string()))
    }
}

fn string_parsing() {
    // Parsing numbers
    let number_str = "42";
    let parsed_number: Result<i32, _> = number_str.parse();
    
    match parsed_number {
        Ok(n) => println!("Parsed number: {}", n),
        Err(e) => println!("Parse error: {}", e),
    }
    
    // Parsing with turbofish syntax
    let float_result = "3.14159".parse::<f64>();
    println!("Parsed float: {:?}", float_result);
    
    // Custom parsing
    let address_str = "1234567890abcdef1234567890abcdef12345678";
    let wallet_address = WalletAddress::from_str(address_str);
    
    match wallet_address {
        Ok(addr) => println!("Valid wallet address: {:?}", addr),
        Err(e) => println!("Invalid address: {}", e),
    }
    
    // Splitting strings
    let csv_data = "apple,banana,orange,grape";
    let fruits: Vec<&str> = csv_data.split(',').collect();
    println!("Fruits: {:?}", fruits);
    
    // Trimming whitespace
    let messy_string = "  Hello, World!  \n";
    let clean_string = messy_string.trim();
    println!("Original: '{}'", messy_string);
    println!("Trimmed: '{}'", clean_string);
    
    // Case conversion
    let mixed_case = "Hello World";
    println!("Uppercase: {}", mixed_case.to_uppercase());
    println!("Lowercase: {}", mixed_case.to_lowercase());
    
    // Checking prefixes and suffixes
    let filename = "transaction_log.txt";
    if filename.starts_with("transaction") {
        println!("This is a transaction file");
    }
    
    if filename.ends_with(".txt") {
        println!("This is a text file");
    }
}

Advanced String Processing

fn advanced_string_processing() {
    // String replacement
    let text = "The quick brown fox jumps over the lazy dog";
    let replaced = text.replace("fox", "cat");
    println!("Replaced: {}", replaced);
    
    // Multiple replacements
    let mut log_entry = "User: alice, Action: transfer, Amount: 100".to_string();
    log_entry = log_entry.replace("alice", "user_123");
    log_entry = log_entry.replace("transfer", "send_tokens");
    println!("Processed log: {}", log_entry);
    
    // Pattern matching with contains
    let commands = vec![
        "create account wallet1",
        "transfer 100 from wallet1 to wallet2",
        "check balance wallet1",
        "delete account wallet2",
    ];
    
    for command in commands {
        if command.contains("create") {
            println!("Account creation command: {}", command);
        } else if command.contains("transfer") {
            println!("Transfer command: {}", command);
        } else if command.contains("balance") {
            println!("Balance query: {}", command);
        } else if command.contains("delete") {
            println!("Account deletion: {}", command);
        }
    }
    
    // Extracting data from strings
    let transaction_data = "tx:abc123,amount:1500,fee:5";
    let mut tx_info = std::collections::HashMap::new();
    
    for pair in transaction_data.split(',') {
        let parts: Vec<&str> = pair.split(':').collect();
        if parts.len() == 2 {
            tx_info.insert(parts[0], parts[1]);
        }
    }
    
    println!("Transaction info: {:?}", tx_info);
    
    // String validation
    fn is_valid_token_symbol(symbol: &str) -> bool {
        symbol.len() >= 3 
            && symbol.len() <= 5 
            && symbol.chars().all(|c| c.is_ascii_uppercase())
    }
    
    let symbols = vec!["SOL", "USDC", "BTC", "a", "TOOLONG", "invalid"];
    
    for symbol in symbols {
        if is_valid_token_symbol(symbol) {
            println!("{} is a valid token symbol", symbol);
        } else {
            println!("{} is NOT a valid token symbol", symbol);
        }
    }
}

🏗️ Practical Example: Transaction Log Processor

Let's build a comprehensive transaction log processor that demonstrates all collection concepts:

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

#[derive(Debug, Clone)]
enum TransactionType {
    Transfer,
    Mint,
    Burn,
    Stake,
    Unstake,
}

impl std::str::FromStr for TransactionType {
    type Err = String;
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "transfer" => Ok(TransactionType::Transfer),
            "mint" => Ok(TransactionType::Mint),
            "burn" => Ok(TransactionType::Burn),
            "stake" => Ok(TransactionType::Stake),
            "unstake" => Ok(TransactionType::Unstake),
            _ => Err(format!("Unknown transaction type: {}", s)),
        }
    }
}

#[derive(Debug, Clone)]
enum Status {
    Success,
    Failed(String),
    Pending,
}

#[derive(Debug, Clone)]
struct LogEntry {
    timestamp: String,
    tx_id: String,
    tx_type: TransactionType,
    from_account: Option<String>,
    to_account: Option<String>,
    amount: u64,
    status: Status,
    gas_fee: u64,
}

#[derive(Debug, Default)]
struct AccountSummary {
    total_sent: u64,
    total_received: u64,
    transaction_count: u32,
    last_activity: String,
    failed_transactions: u32,
}

struct TransactionProcessor {
    logs: Vec<LogEntry>,
    accounts: HashMap<String, AccountSummary>,
    daily_volumes: BTreeMap<String, u64>,
    error_patterns: HashMap<String, u32>,
}

impl TransactionProcessor {
    fn new() -> Self {
        Self {
            logs: Vec::new(),
            accounts: HashMap::new(),
            daily_volumes: BTreeMap::new(),
            error_patterns: HashMap::new(),
        }
    }
    
    fn parse_log_line(&self, line: &str) -> Result<LogEntry, String> {
        // Parse format: "timestamp|tx_id|type|from|to|amount|status|fee"
        let parts: Vec<&str> = line.split('|').collect();
        
        if parts.len() != 8 {
            return Err("Invalid log format".to_string());
        }
        
        let tx_type = parts[2].parse::<TransactionType>()?;
        
        let from_account = if parts[3].is_empty() { 
            None 
        } else { 
            Some(parts[3].to_string()) 
        };
        
        let to_account = if parts[4].is_empty() { 
            None 
        } else { 
            Some(parts[4].to_string()) 
        };
        
        let amount = parts[5].parse::<u64>()
            .map_err(|_| "Invalid amount".to_string())?;
        
        let gas_fee = parts[7].parse::<u64>()
            .map_err(|_| "Invalid gas fee".to_string())?;
        
        let status = match parts[6] {
            "SUCCESS" => Status::Success,
            "PENDING" => Status::Pending,
            error_msg => Status::Failed(error_msg.to_string()),
        };
        
        Ok(LogEntry {
            timestamp: parts[0].to_string(),
            tx_id: parts[1].to_string(),
            tx_type,
            from_account,
            to_account,
            amount,
            status,
            gas_fee,
        })
    }
    
    fn add_log_entry(&mut self, entry: LogEntry) {
        // Extract date from timestamp for daily volume tracking
        let date = entry.timestamp.split('T').next()
            .unwrap_or("unknown")
            .to_string();
        
        // Update daily volume for successful transactions
        if matches!(entry.status, Status::Success) {
            *self.daily_volumes.entry(date).or_insert(0) += entry.amount;
        }
        
        // Track error patterns
        if let Status::Failed(error) = &entry.status {
            *self.error_patterns.entry(error.clone()).or_insert(0) += 1;
        }
        
        // Update account summaries
        if let Some(from) = &entry.from_account {
            let summary = self.accounts.entry(from.clone()).or_default();
            summary.transaction_count += 1;
            summary.last_activity = entry.timestamp.clone();
            
            match entry.status {
                Status::Success => summary.total_sent += entry.amount,
                Status::Failed(_) => summary.failed_transactions += 1,
                Status::Pending => {}
            }
        }
        
        if let Some(to) = &entry.to_account {
            let summary = self.accounts.entry(to.clone()).or_default();
            summary.transaction_count += 1;
            summary.last_activity = entry.timestamp.clone();
            
            if matches!(entry.status, Status::Success) {
                summary.total_received += entry.amount;
            }
        }
        
        self.logs.push(entry);
    }
    
    fn process_log_file(&mut self, log_data: &str) -> Result<usize, String> {
        let mut processed_count = 0;
        let mut error_count = 0;
        
        for (line_num, line) in log_data.lines().enumerate() {
            if line.trim().is_empty() {
                continue;
            }
            
            match self.parse_log_line(line.trim()) {
                Ok(entry) => {
                    self.add_log_entry(entry);
                    processed_count += 1;
                }
                Err(e) => {
                    println!("Error parsing line {}: {} ({})", line_num + 1, e, line);
                    error_count += 1;
                }
            }
        }
        
        if error_count > 0 {
            println!("Processed {} entries with {} errors", processed_count, error_count);
        }
        
        Ok(processed_count)
    }
    
    fn get_top_accounts_by_volume(&self, limit: usize) -> Vec<(&String, &AccountSummary)> {
        let mut accounts: Vec<_> = self.accounts.iter().collect();
        accounts.sort_by(|a, b| {
            (b.1.total_sent + b.1.total_received)
                .cmp(&(a.1.total_sent + a.1.total_received))
        });
        accounts.into_iter().take(limit).collect()
    }
    
    fn get_most_active_accounts(&self, limit: usize) -> Vec<(&String, &AccountSummary)> {
        let mut accounts: Vec<_> = self.accounts.iter().collect();
        accounts.sort_by(|a, b| b.1.transaction_count.cmp(&a.1.transaction_count));
        accounts.into_iter().take(limit).collect()
    }
    
    fn analyze_transaction_patterns(&self) -> HashMap<String, u32> {
        let mut patterns = HashMap::new();
        
        for entry in &self.logs {
            let pattern = match entry.tx_type {
                TransactionType::Transfer => {
                    if entry.amount > 1000000 {
                        "large_transfer"
                    } else {
                        "small_transfer"
                    }
                }
                TransactionType::Mint => "token_creation",
                TransactionType::Burn => "token_destruction",
                TransactionType::Stake => "staking_activity",
                TransactionType::Unstake => "unstaking_activity",
            };
            
            *patterns.entry(pattern.to_string()).or_insert(0) += 1;
        }
        
        patterns
    }
    
    fn find_suspicious_accounts(&self) -> Vec<&String> {
        self.accounts.iter()
            .filter(|(_, summary)| {
                // High failure rate or unusual activity patterns
                let failure_rate = summary.failed_transactions as f64 / summary.transaction_count as f64;
                failure_rate > 0.3 || summary.transaction_count > 100
            })
            .map(|(address, _)| address)
            .collect()
    }
    
    fn get_daily_volume_trend(&self) -> Vec<(&String, &u64)> {
        self.daily_volumes.iter().collect()
    }
    
    fn search_transactions(&self, query: &str) -> Vec<&LogEntry> {
        self.logs.iter()
            .filter(|entry| {
                entry.tx_id.contains(query) ||
                entry.from_account.as_ref().map_or(false, |addr| addr.contains(query)) ||
                entry.to_account.as_ref().map_or(false, |addr| addr.contains(query))
            })
            .collect()
    }
    
    fn generate_report(&self) -> String {
        let mut report = String::new();
        
        report.push_str("=== TRANSACTION LOG ANALYSIS REPORT ===\n\n");
        
        // Basic statistics
        report.push_str(&format!("Total transactions processed: {}\n", self.logs.len()));
        report.push_str(&format!("Unique accounts: {}\n", self.accounts.len()));
        report.push_str(&format!("Days of activity: {}\n\n", self.daily_volumes.len()));
        
        // Top accounts by volume
        report.push_str("TOP 5 ACCOUNTS BY VOLUME:\n");
        for (i, (address, summary)) in self.get_top_accounts_by_volume(5).iter().enumerate() {
            let total_volume = summary.total_sent + summary.total_received;
            report.push_str(&format!(
                "{}. {} - Volume: {}, Transactions: {}\n",
                i + 1, address, total_volume, summary.transaction_count
            ));
        }
        
        // Transaction patterns
        report.push_str("\nTRANSACTION PATTERNS:\n");
        let patterns = self.analyze_transaction_patterns();
        for (pattern, count) in patterns {
            report.push_str(&format!("- {}: {}\n", pattern, count));
        }
        
        // Error analysis
        report.push_str("\nERROR ANALYSIS:\n");
        for (error, count) in &self.error_patterns {
            report.push_str(&format!("- {}: {} occurrences\n", error, count));
        }
        
        // Suspicious accounts
        let suspicious = self.find_suspicious_accounts();
        if !suspicious.is_empty() {
            report.push_str("\nSUSPICIOUS ACCOUNTS:\n");
            for account in suspicious {
                report.push_str(&format!("- {}\n", account));
            }
        }
        
        report
    }
}

fn main() {
    let mut processor = TransactionProcessor::new();
    
    // Sample log data
    let log_data = r#"
2024-01-15T10:30:00Z|tx001|transfer|wallet1|wallet2|500000|SUCCESS|5000
2024-01-15T10:31:00Z|tx002|mint|system|wallet1|1000000|SUCCESS|3000
2024-01-15T10:32:00Z|tx003|transfer|wallet2|wallet3|250000|SUCCESS|5000
2024-01-15T10:33:00Z|tx004|transfer|wallet1|wallet4|750000|INSUFFICIENT_FUNDS|0
2024-01-15T10:34:00Z|tx005|burn|wallet3||100000|SUCCESS|2000
2024-01-15T10:35:00Z|tx006|stake|wallet1|validator1|200000|SUCCESS|1000
2024-01-16T09:00:00Z|tx007|transfer|wallet4|wallet1|300000|SUCCESS|5000
2024-01-16T09:01:00Z|tx008|unstake|validator1|wallet1|200000|SUCCESS|1000
"#;
    
    // Process the log data
    match processor.process_log_file(log_data) {
        Ok(count) => println!("Successfully processed {} transactions", count),
        Err(e) => println!("Error processing logs: {}", e),
    }
    
    // Generate and display report
    println!("{}", processor.generate_report());
    
    // Demonstrate search functionality
    println!("=== SEARCH RESULTS ===");
    let search_results = processor.search_transactions("wallet1");
    println!("Transactions involving 'wallet1': {}", search_results.len());
    
    for tx in search_results {
        println!("- {}: {:?} {} tokens", tx.tx_id, tx.tx_type, tx.amount);
    }
    
    // Show daily volume trend
    println!("\n=== DAILY VOLUME TREND ===");
    for (date, volume) in processor.get_daily_volume_trend() {
        println!("{}: {} tokens", date, volume);
    }
}

🎯 Day 5 Practice Exercises

Exercise 1: Token Registry

Build a token registry system:

use std::collections::HashMap;

#[derive(Debug, Clone)]
struct TokenInfo {
    symbol: String,
    name: String,
    decimals: u8,
    total_supply: u64,
    holders: HashMap<String, u64>,
}

struct TokenRegistry {
    tokens: HashMap<String, TokenInfo>, // mint address -> token info
}

impl TokenRegistry {
    fn new() -> Self {
        // TODO: Implement
        todo!()
    }
    
    fn register_token(&mut self, mint: String, info: TokenInfo) -> Result<(), String> {
        // TODO: Implement token registration
        todo!()
    }
    
    fn transfer(&mut self, mint: &str, from: &str, to: &str, amount: u64) -> Result<(), String> {
        // TODO: Implement token transfer between holders
        todo!()
    }
    
    fn get_balance(&self, mint: &str, holder: &str) -> Option<u64> {
        // TODO: Get holder balance for specific token
        todo!()
    }
    
    fn get_top_holders(&self, mint: &str, limit: usize) -> Vec<(String, u64)> {
        // TODO: Get top holders for a token
        todo!()
    }
}

Exercise 2: Log Parser

Create a flexible log parsing system:

#[derive(Debug)]
enum LogLevel {
    Info,
    Warning,
    Error,
    Debug,
}

#[derive(Debug)]
struct LogMessage {
    timestamp: String,
    level: LogLevel,
    component: String,
    message: String,
}

struct LogParser {
    messages: Vec<LogMessage>,
}

impl LogParser {
    fn parse_line(&self, line: &str) -> Result<LogMessage, String> {
        // TODO: Parse log line format: "[timestamp] LEVEL component: message"
        todo!()
    }
    
    fn filter_by_level(&self, level: LogLevel) -> Vec<&LogMessage> {
        // TODO: Filter messages by log level
        todo!()
    }
    
    fn search_messages(&self, query: &str) -> Vec<&LogMessage> {
        // TODO: Search in message content
        todo!()
    }
    
    fn group_by_component(&self) -> HashMap<String, Vec<&LogMessage>> {
        // TODO: Group messages by component
        todo!()
    }
}

Exercise 3: Word Counter

Build an advanced word counting and analysis tool:

use std::collections::HashMap;

struct TextAnalyzer {
    word_counts: HashMap<String, u32>,
    total_words: u32,
}

impl TextAnalyzer {
    fn analyze_text(&mut self, text: &str) {
        // TODO: Count word frequencies, handle punctuation
        todo!()
    }
    
    fn most_common_words(&self, n: usize) -> Vec<(String, u32)> {
        // TODO: Return top N most common words
        todo!()
    }
    
    fn words_starting_with(&self, prefix: &str) -> Vec<&String> {
        // TODO: Find words with specific prefix
        todo!()
    }
    
    fn get_statistics(&self) -> TextStats {
        // TODO: Calculate various text statistics
        todo!()
    }
}

struct TextStats {
    total_words: u32,
    unique_words: u32,
    average_word_length: f64,
    longest_word: String,
}

🚀 What's Next?

You've now mastered:

  • Vector operations and performance optimization
  • HashMap and BTreeMap for key-value storage
  • String creation, manipulation, and parsing
  • Collection iteration and transformation
  • Real-world data processing patterns

Next up is our Practical Build Session where you'll combine everything you've learned to build real Solana-related projects!

Solana Connection

Collections are fundamental in Solana development: managing account data, processing transaction batches, storing program state, and analyzing blockchain data. The patterns you learned today will be essential for building efficient Solana applications!

Solana Assistant

AI-powered documentation helper

Welcome to Solana Assistant

Ask specific questions about Solana development:

Ask specific questions for better results400px
    Rust Collections and String Handling (Vectors, HashMap, BTreeMap) | learn.sol