Skip to content

Instantly share code, notes, and snippets.

@rsachdeva
Last active December 22, 2024 19:38
Show Gist options
  • Save rsachdeva/fb5ecf6cca34ceebf5643ebe120ded6a to your computer and use it in GitHub Desktop.
Save rsachdeva/fb5ecf6cca34ceebf5643ebe120ded6a to your computer and use it in GitHub Desktop.
Rust Snippets for Vector, String, HashMap, One Line Iterators and Range: Most common Structures for Data

Rust Snippets for Vector, String, HashMap, One Line Iterators and Range: Most common Structures for Data

Table of Contents

Vector

  • Creation: Vec::new(), vec![] macro
  • Adding elements: push()
  • Adding multiple elements: extend()
  • Removing elements: pop(), remove()
  • Accessing elements: indexing vec[i], get(), get_mut()
// get
fn run() {
    // Type annotations are just added to show slice &[i32].
    // So `get` supports range as input. Returns slice
    let v = vec![1, 2, 3];
    let x: Option<&i32> = v.get(1);      // Single element
    let y: Option<&[i32]> = v.get(0..2);   // Slice range


    let mut x = [1, 2, 3];

    // get_mut returns mutable reference for val which can be modified for value inside Vector
    if let Some(elem) = x.get_mut(1) {
        *elem = 9;
    }

    println!("x is {:?}", x); // [1, 9, 3]
}


// user get with slice and let else
fn run() {
    // The conversion of String to Vec at the start can be a key performance optimization
    let needle_chars: Vec<char> = needle.chars().collect();
    let needle_length = needle_chars.len();
    let haystack_chars: Vec<char> = haystack.chars().collect();
    for (idx_h, char_h) in haystack.chars().enumerate() {
        let Some(slice_h) = haystack_chars.get(idx_h..idx_h + needle_length) else {
            break;
        };
        if slice_h == needle_chars {
            return idx_h as i32;
        }
    }
    -1
}
  • Iteration: for item in numbers, numbers.iter(), numbers.enumerate()
fn run() {
    let numbers = vec![1, 2, 3, 4, 5];

    // Direct iteration (takes ownership)
    for number in numbers {
        println!("{}", number);
    }
    // same as
    for number in numbers.into_iter() {
        println!("{}", number);
    }

    // Iterator (borrows)
    for number in numbers.iter() {
        println!("{}", number);
    }
    // With index
    for (index, number) in numbers.enumerate() {
        println!("Index: {}, Number: {}", index, number);
    }
}
  • Slicing: &vec[start..end]
fn run() {
    // Example with Vec<char>
    let chars: Vec<char> = vec!['h', 'e', 'l', 'l', 'o'];
    let slice1 = &chars[1..4];  // gets 'ell'
    println!("Char slice: {:?}", slice1);

    // Example with Vec<i32>
    let numbers: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let slice2 = &numbers[2..5];  // gets [3, 4, 5]
    println!("Number slice: {:?}", slice2);

    // Using inclusive range
    let slice3 = &numbers[2..=5];  // gets [3, 4, 5, 6]
    println!("Inclusive slice: {:?}", slice3);
}
  • Splitting: split_at(), split_at_mut()
fn run() {
    let numbers = vec![1, 2, 3, 4, 5, 6];

    // Immutable split
    let (first, second) = numbers.split_at(3);
    println!("First half: {:?}", first);   // [1, 2, 3]
    println!("Second half: {:?}", second); // [4, 5, 6]

    let mut numbers = vec![1, 2, 3, 4, 5, 6];

    // Mutable split
    let (first, second) = numbers.split_at_mut(3);
    first[0] = 10;
    second[0] = 20;
}


fn run() {
    let mut numbers = vec![1, 2, 3, 4, 5, 6];

    // Split the vector at index 3
    let (first_half, second_half) = numbers.split_at_mut(3);

    // Now we can modify both halves independently
    first_half[0] = 10;    // Modifies [1, 2, 3]
    second_half[0] = 20;   // Modifies [4, 5, 6]

    println!("First half: {:?}", first_half);   // [10, 2, 3]
    println!("Second half: {:?}", second_half); // [20, 5, 6]
    println!("Full vector: {:?}", numbers);     // [10, 2, 3, 20, 5, 6]
}


fn run() {
    let original_vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let mut new_vec = Vec::new();

    for (index, &element) in original_vec.iter().enumerate() {
        if index != 2 {  // Skip the 3rd element (index 2)
            new_vec.push(element);
        }
    }

    println!("Original vector: {:?}", original_vec);
    println!("New vector (skipping 3rd position): {:?}", new_vec);
}


fn run() {
    let mut original_vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // Use split_at_mut to get mutable slices
    let (first_part, second_part) = original_vec.split_at_mut(2);

    // Create a new vector
    let mut new_vec = Vec::new();

    // Extend with the first two elements
    new_vec.extend_from_slice(first_part);

    // Skip the third element and extend with the rest
    new_vec.extend_from_slice(&second_part[1..]);

    println!("Original vector: {:?}", original_vec);
    println!("New vector (skipping 3rd position): {:?}", new_vec);
}
  • Conversion: collect() to create from iterators
fn run() {
    // String to Vec<char>
    let given = word.to_string();
    let mut chars: Vec<char> = given.chars().collect();
    // also works with &str
    let text: &str = "hello world";
    let chars: Vec<char> = text.chars().collect();

    // HashMap<String, i32> to Vec<(String, i32)>
    let mut hash_map: HashMap<String, i32> = HashMap::new();
    hash_map.insert("apple".to_string(), 5);
    hash_map.insert("banana".to_string(), 2);
    hash_map.insert("cherry".to_string(), 8);
    let vec: Vec<(String, i32)> = hash_map.into_iter().collect();

    //  HashMap<String, Vec<String>> to 
    // values of HashMap Vec<Vec<String>> 
    // and to 
    // keys of HashMap Vec<String> 
    let mut lookup: HashMap<String, Vec<String>> = HashMap::new();
    lookup.insert("fruits".to_string(), vec!["apple".to_string(), "banana".to_string()]);
    lookup.insert("vegetables".to_string(), vec!["carrot".to_string(), "potato".to_string()]);
    // same as let vec: Vec<Vec<String>> = lookup.into_iter().map(|(_, v)| v).collect();
    let values: Vec<Vec<String>> = lookup.values().cloned().collect();
    let vec: Vec<String> = lookup.keys().cloned().collect();
}
  • 2D operations: vec![vec![0; cols]; rows] for matrix creation
  • Length: len()
  • Check if empty: is_empty()
  • Sorting: sort(), sort_by(), sort_unstable(), sort_unstable_by()
fn run() {
    #[derive(Debug)]
    struct Student {
        name: String,
        grade: u8,
    }

    let mut students = vec![
        Student { name: "Alice".to_string(), grade: 85 },
        Student { name: "Bob".to_string(), grade: 85 },
        Student { name: "Charlie".to_string(), grade: 82 },
    ];

    // Sorts the slice with a key extraction function, preserving the initial order of equal elements.
    // The key in sort_by_key() refers to the value we want to sort by - it's a transformation function that extracts a comparable value from each element.
    // It's more like a "sorting key" or "comparison key" rather than a dictionary/hash key.
    students.sort_by_key(|s| s.grade);

    students.sort_by_key(|s| s.name.clone());

    println!("students are {:?}", students);
}
  • compare vector to slice directly:
fn run() {
    // You can compare a slice with a Vec directly in Rust since Vec implements Deref to slice. Here's how:

    let vec = vec![1, 2, 3];
    let slice = &[1, 2, 3];
    let are_equal = vec == slice; // true

    // Also works with string types
    let string_vec = vec!["hello", "world"];
    let string_slice = &["hello", "world"];
    let are_equal = string_vec == string_slice; // true
}
  • swap elements at given indexes:
fn run(zero_idx: usize, non_zero_idx: usize, nums: &mut Vec<i32>) {
    // let tmp = nums[zero_idx];
    // nums[zero_idx] = nums[non_zero_idx];
    // nums[non_zero_idx] = tmp;
    nums.swap(zero_idx, non_zero_idx);
}

Back to Table of Contents

String

  • Creation: String::from(), to_string()
  • Adding elements: push() for single characters: final_word.push('a')
  • Adding multiple elements: push_str() for string slices: final_word.push_str("hello") Note: String has an append() method that takes ownership of another String and appends it to the current String. It's similar to push_str() but consumes the argument String instead of borrowing it.
  • Removing elements: remove last character: pop()
  • Slicing: get and &str[start..end]
fn run() {
    let owned = String::from("world");
    let y: Option<&str> = owned.get(1..3);

    // str slice
    let text = "hello";
    let z: Option<&str> = text.get(1..3);

    // y and z are Option<&str>
    // Type annotations are just added to show slice &str.
    // So `get` supports range as input. Returns slice. 
}
  • Conversion: chars().collect(), iter().collect(), into_iter().collect()
 fn run() {
    // Vec<chars> to String
    let chars: Vec<char> = vec!['a', 'b', 'c', 'd', 'e'];
    // Rust provides the FromIterator trait implementation for String that can work with both references (iter()) and owned values (into_iter()). 
    // When using iter(), the characters are implicitly copied since char is a Copy type, making it just as valid as using into_iter().
    // let s: String = chars.into_iter().collect();
    // this also works:
    // let s1: String = chars.collect();
    let s: String = chars.iter().collect();
}
  • Checking: starts_with(), ends_with(), contains(), is_empty()
  • Iteration: chars() for character-wise iteration
  • Splitting: split() to parse string into substrings
  • Use trim() to remove whitespace in strings.
  • Use to_lowercase() or to_uppercase() to ensure that you’re comparing strings in the same case
  • Use the regex crate for more complex string manipulation
fn run() {
    // whitespace-separated words
    let sentence = "Rust is a great language for systems programming".to_string();
    let words: Vec<String> = sentence.split_whitespace().map(String::from).collect();
    println!("Words: {:?}", words);

    // First and last elements
    let first_word = words.first().cloned().unwrap_or_default();
    let last_word = words.last().cloned().unwrap_or_default();
    println!("First word: {}, Last word: {}", first_word, last_word);

    // comma-separated values CSV
    let csv = "apple,banana,cherry,date,elderberry".to_string();
    let fruits: Vec<String> = csv.split(',').map(String::from).collect();
    println!("Fruits: {:?}", fruits);

    // with vector First, last, and third elements
    // the clone here is for Option<&String> to String
    // could use unwrap and then clone
    let first_fruit = fruits.first().cloned().unwrap_or_default();
    let last_fruit = fruits.last().cloned().unwrap_or_default();
    let third_fruit = fruits.get(2).cloned().unwrap_or_default();
    println!("First fruit: {}, Last fruit: {}, Third fruit: {}", first_fruit, last_fruit, third_fruit);

    // with CSV splitting
    let mut splits = csv.split(',');
    let first = splits.next().unwrap_or("").to_string();
    let last = splits.next_back().unwrap_or("").to_string();
    let third_alt = csv.split(',').nth(2).unwrap_or("").to_string();
    println!("First (alt): {}, Last (alt): {}, Third (alt): {}", first, last, third_alt);
}
  • Replacement: replace() to manipulate string by replacing substrings
  • Parsing: parse() to convert string to other types
  • Length: .len() counts bytes, .chars().count() counts Unicode scalar values.
  • Check if empty: is_empty()
  • Sorting: Convert to char array, sort, then collect back to String
fn run() {
    let s = String::from("hello");
    let mut chars: Vec<char> = s.chars().collect();
    chars.sort_unstable();
    let sorted_string: String = chars.into_iter().collect();
    println!("Sorted string: {}", sorted_string);
}

Back to Table of Contents

HashMap

  • Creation: HashMap::new()
  • Entry API: entry(key).or_insert(default_value), entry(key).and_modify(operation).or_insert(default_value)
fn run() {
    use std::collections::HashMap;

    let mut lookup: HashMap<String, usize> = HashMap::new();
    lookup.insert("key".to_string(), 9);
    println!("lookup is {:?}", lookup); // {"key": 9}

    // or_insert returns mutable reference for val which can be directly modified inside HashMap
    let val = lookup.entry("key".to_string()).or_insert(0);
    *val = 1000;
    println!("lookup is {:?}", lookup); // {"key": 1000}

    // with add_modify
    lookup.entry("key".to_string()).and_modify(|v| *v += 1);
    lookup.entry("key".to_string()).and_modify(|v| *v += 1).or_insert(0);

    println!("Lookup contents: {:?}", lookup); // Lookup contents: {"key": 1002}
}
  • Insertion: insert(key, value)
fn run() {
    // Both approaches achieve the same result of decrementing the count for the key t_ch. The insert method will simply override the existing value with the new decremented count. While the entry API provides a more fluent interface for these operations, using insert directly is perfectly valid for updating existing values in a HashMap.
    // hash_s.entry(t_ch).and_modify(|count| *count -= 1);
    hash_s.insert(t_ch, count - 1);
}
  • Removal: remove(&key)
  • Retrieval: get(&key), contains_key(&key), hashmap[key], get_mut(&key)
fn run() {
    // Both code snippets are functionally equivalent. They both handle the case of incrementing an existing count or initializing it to 1 if the key doesn't exist. The entry API version does it in one fluid chain, while the get/insert approach breaks it into two distinct steps. Both are valid ways to achieve the same result in a HashMap.
    // hash_s
    //     .entry(s_ch)
    //     .and_modify(|count| *count += 1)
    //     .or_insert(1);
    let current_count = hash_s.get(&s_ch).unwrap_or(&0);
    hash_s.insert(s_ch, current_count + 1);

    // get_mut returns mutable reference for val which can be modified for value inside Hash
    if let Some(count) = hash_s.get_mut(&s_ch) {
        *count += 1;
    } else {
        hash_s.insert(s_ch, 1);
    }
}
  • Iteration: for (key, value) in lookup.iter()
  • Length: len()
  • Check if empty: is_empty()
    • Sorting: Convert to Vec<(&K, &V)> and use sort_by_key() or sort_by()
fn run() {
    use std::collections::HashMap;

    let mut lookup: HashMap<String, i32> = HashMap::new();
    lookup.insert("apple".to_string(), 5);
    lookup.insert("banana".to_string(), 2);
    lookup.insert("cherry".to_string(), 8);

    // lookup.entry("key".to_string()).and_modify(|v| *v += 1).or_insert(0);
    lookup
        .entry("apple".to_string())
        .and_modify(|x| *x += 100)
        .or_insert(0);

    let mut vec: Vec<(&String, &i32)> = lookup.iter().collect();
    // Unsorted values: [("cherry", 8), ("banana", 2), ("apple", 105)]
    println!("Unsorted vector: {:?}", vec);

    vec.sort_by_key(|&(_k, v)| v);
    // Sorted vector by values: [("banana", 2), ("cherry", 8), ("apple", 105)]
    println!("Sorted vector by values: {:?}", vec);
    // same with sort_by
    vec.sort_by(|a, b| a.1.cmp(b.1));

    vec.sort_by_key(|&(k, _v)| k);
    // Sorted vector by keys: [("apple", 105), ("banana", 2), ("cherry", 8)]
    println!("Sorted vector by keys: {:?}", vec);

    // same with sort_by
    vec.sort_by(|a, b| a.0.cmp(b.0));
    println!("Sorted vector by keys: {:?}", vec);
}

Back to Table of Contents

One line iterators

  • one line iterators are useful (besides collect already included above)
fn run() {
    // type annotations in some cases are just to briefly show type being returned
    let vec = vec![1, 2, 3];
    // count() gives us the length of the resulting iterator after applying operations like filter(), map(), or 
    //  other transformations. It's particularly useful when you want to count elements that meet 
    // certain conditions or when working with iterator adaptors.
    // The pattern is especially common when dealing with generic iterators where you don't have 
    //direct access to a len() method.
    let count: usize = vec.iter().filter(|&&x| x % 2 == 0).count();;
    let sum: i32 = vec.iter().sum();
    let has_even: bool = vec.iter().any(|x| x % 2 == 0);
    let find_two: Option<&i32> = vec.iter().find(|&&x| x == 2);
    let first: Option<&i32> = vec.iter().next();
    let max: Option<&i32> = vec.iter().max();
    let min: Option<&i32> = vec.iter().min();
    let last: Option<&i32> = vec.iter().last();
    let skip_first: Vec<&i32> = vec.iter().skip(1).collect();  // Skips first element
}

Back to Table of Contents

Range

  • Basic range: 0..n excludes n, 0..=n includes n
  • Reverse range: (0..n).rev() counts down
  • Bounds checking: 0..nums.len() - 1 elegantly handles index + 1 operations
fn run() {
    // Forward iteration (3 is excluded)
    for i in 0..3 {
        println!("{}", i);  // prints 0, 1, 2
    }

    // Inclusive range
    for i in 0..=3 {
        println!("{}", i);  // prints 0, 1, 2, 3
    }

    // Reversed iteration (3 is excluded)
    for i in (0..3).rev() {
        println!("Reversed: {}", i);
    }
    // Prints:
    // Reversed: 2
    // Reversed: 1
    // Reversed: 0

    // Safe index + 1 operations
    for idx in 0..nums.len() - 1 {
        let current = nums[idx];
        let next = nums[idx + 1];  // always valid
    }

    // Range patterns in match expressions
    match n {
        0..=3 => println!("small number"),
        4..=7 => println!("medium number"),
        _ => println!("large number"),
    }

    // Step by using step_by()
    for i in (0..10).step_by(2) {
        println!("{}", i);  // prints 0, 2, 4, 6, 8
    }

    // Range with chars
    for c in 'a'..='z' {
        println!("{}", c);  // prints a through z
    }

    // Range bounds checking
    let nums = vec![1, 2, 3, 4, 5];
    let slice = &nums[1..3];  // safe slice from index 1 to 3 (exclusive) so gets [2, 3]
}
  • Contains: range.contains(&value) to check if a range contains a value
fn run() {
    let range = 1..=5;
    let value = 3;
    let is_in_range = range.contains(&value);
    println!("Is {} in range? {}", value, is_in_range);  // prints true
}

Back to Table of Contents

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment