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 Wallet Manager Challenge (Enums + Pattern Matching)
Build a Solana-style wallet manager using enums and pattern matching to handle account types, transaction states, and robust error handling.
NFT Metadata Manager and DEX Order Book Challenges
Build an NFT metadata manager and a DEX order book using Rust collections and string processing to practice production patterns common in Solana apps.