learn.sol

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