A1 FA25 Final Exam Review
Table of Contents:
- Preliminaries
- 1. HashMap and the Entry API
- 2. HashSet and Set Operations
- 3. BTreeMap and Ordered Collections
- 4. VecDeque and Circular Buffers
- 5. Iterators and Iterator Chains
- 6. Algorithm Complexity
- 7. Option and Result Types
- Final Tips for the Exam
Suggested way to use this review material
- The material is organized by major topics.
- For each topic, there are:
- high level overview
- examples
- true/false questions
- find the bug questions
- predict the output questions
- coding challenges
- Try to answer the questions without peeking at the solutions.
- This material focuses on the topics covered in the final third of the course, building on what you learned for midterms 1 and 2.
Exam Format:
The exam will be in four parts:
- Part 1 (10 pts): 5 questions, 2 points each -- select all that are true
- Part 2 (16 pts): 4 questions, 4 points each -- find the bug in the code and fix it
- Part 3 (12 pts): 4 questions, 3 points each -- Predict the output and explain why
- Part 4 (12 pts): 2 questions, 6 points each -- hand-coding problems
Total Points: 50
Suggested time budget for each part:
- Part 1: (~10 min)
- Part 2: (~16 min)
- Part 3: (~12 min)
- Part 4: (~22 min)
for a total of 60 minutes and then another 60 minutes to check your work (if needed).
Preliminaries
The final exam is cumulative but emphasizes the material from the final third of the course. You should be comfortable with:
- Basic Rust syntax (functions, variables, types) (see midterm 1 review)
- Structs, enums, and pattern matching
- Ownership, borrowing, and references
- Generics and traits
- Iterators and closures
See a1 midterm 2 review for more details.
This review focuses on new material: collections (HashMap, HashSet, BTreeMap, VecDeque) and algorithm complexity.
References and Dereferencing
When References Are Created
References are created with &:
#![allow(unused)] fn main() { let x = 5; let r = &x; // r is &i32 let s = "hello"; // s is already &str (string slice) let v = vec![1, 2, 3]; let slice = &v[..]; // slice is &[i32] }
And are common patterns in Rust code. For example, to sum a slice of integers:
#![allow(unused)] fn main() { fn process_ints(ints: &[i32]) -> i32 { let mut sum = 0; for int in ints { sum += *int; } sum } let ints = [1, 2, 3]; println!("sum: {}", process_ints(&ints)); }
When Double References (&&) Occur
Double references commonly appear when:
Iterating over a slice of references:
#![allow(unused)] fn main() { fn process(words: &[&str]) { for word in words { // word is &&str // Rust auto-dereferences `word: &&str` to `word: &str` print!("word: {}, len: {} ", word, word.len()); } println!(); } let words = vec!["art", "bees"]; process(&words); }
Automatic Dereferencing
Rust automatically dereferences in several situations:
1. Method calls (auto-deref):
#![allow(unused)] fn main() { let s = String::from("hello"); let r = &s; let rr = &&s; // All of these work - Rust auto-derefs to call len() s.len(); // String::len(&s) r.len(); // auto-derefs &String to String rr.len(); // auto-derefs &&String through &String to String }
2. Deref coercion in function arguments:
#![allow(unused)] fn main() { fn print_len(s: &str) { println!("{}", s.len()); } let owned = String::from("hello"); print_len(&owned); // &String coerces to &str automatically print_len("hello"); // Already a &str }
3. Comparison operators:
#![allow(unused)] fn main() { let x = 5; let r = &x; // assert!(r == 5); // ERROR! r is a reference, not a value assert!(r == &5); // Compares values, not addresses, but types must match assert!(*r == 5); // Explicit deref to i32 also works }
When Explicit Dereferencing (*) Is Required
1. Assigning to or modifying the underlying value:
#![allow(unused)] fn main() { let mut x = 5; let r = &mut x; *r += 1; // Must deref to modify x println!("x: {}", x); }
2. When types don't match and coercion doesn't apply:
#![allow(unused)] fn main() { let words: &[&str] = &["a", "b"]; for word in words { // word is &&str, but HashMap wants &str as key let key: &str = *word; // Explicit deref needed } }
3. Using entry() or insert() with reference keys:
#![allow(unused)] fn main() { use std::collections::HashMap; fn count<'a>(words: &[&'a str]) -> HashMap<&'a str, i32> { let mut map = HashMap::new(); for word in words { // word: &&str *map.entry(*word) // *word dereferences &&str to &str .or_insert(0) += 1; } map } let words = vec!["a", "b"]; println!("{:?}", count(&words)); println!("{:?}", words); }
Quick Reference Table
| Situation | Type | Need explicit *? |
|---|---|---|
| Method calls | &T, &&T, etc. | No (auto-deref) |
Deref coercion (&String → &str) | Function args | No |
Modifying through &mut T | *r = value | Yes |
HashMap key from &&str | entry(*word) | Yes |
| Pattern matching | &x pattern | Alternative to * |
1. HashMap and the Entry API
Module(s)
Quick Review
HashMap<K, V> is a hash table that maps keys to values:
- Keys must implement
HashandEqtraits - O(1) average lookup, insertion, and deletion
- Does NOT maintain insertion order
f64cannot be used directly as a key (doesn't implementHashdue to NaN)
Key Methods:
insert(key, value)- inserts or overwritesget(&key)- returnsOption<&V>get_mut(&key)- returnsOption<&mut V>contains_key(&key)- returnsboolremove(&key)- removes and returnsOption<V>
The Entry API is the idiomatic way to insert-or-update:
#![allow(unused)] fn main() { *map.entry(key).or_insert(default) += 1; }
.entry(key)returns anEntryenum, which can be eitherOccupiedorVacant- Entry API methods:
or_insert(default)inserts the default value if the key is not presentor_insert_with(f)inserts the value returned by the function if the key is not presentor_default()inserts the default value for the type if the key is not presentand_modify(f)modifies the value if the key is present
Examples
#![allow(unused)] fn main() { use std::collections::HashMap; // Basic HashMap usage let mut scores = HashMap::new(); // .insert returns None if the key was not in the map let mut result = scores.insert("Alice", 85); println!("result: {:?}", result); // None, because "Alice" was not in the map // .insert() returns Some(&value), where value is the old value if the key was // already in the map result = scores.insert("Alice", 87); println!("result: {:?}", result); // Some(&85) scores.insert("Bob", 90); // get() returns Option println!("scores.get(\"Alice\"): {:?}", scores.get("Alice")); // Some(&85) println!("scores.get(\"Carol\"): {:?}", scores.get("Carol")); // None // unwrap_or provides a default println!("scores.get(\"Carol\").unwrap_or(&0): {:?}", scores.get("Carol").unwrap_or(&0)); // &0 }
#![allow(unused)] fn main() { use std::collections::HashMap; // Entry API for counting let mut word_counts = HashMap::new(); for word in ["apple", "banana", "apple"] { *word_counts.entry(word).or_insert(0) += 1; } // word_counts: {"apple": 2, "banana": 1} println!("word_counts: {:?}", word_counts); }
#![allow(unused)] fn main() { use std::collections::HashMap; // Entry API - or_insert only inserts if key is missing let mut map = HashMap::new(); *map.entry("a").or_insert(0) += 1; // a = 1 *map.entry("a").or_insert(10) += 1; // a = 2 (10 is NOT used, key exists) println!("map: {:?}", map); }
True/False Questions
-
T/F: Keys in a HashMap must implement the
HashandEqtraits. -
T/F: HashMap maintains insertion order of elements.
-
T/F:
f64can be used directly as a HashMap key. -
T/F: The
entry()API allows efficient insert-or-update operations. -
T/F:
map.get(&key)returnsVdirectly. -
T/F: Looking up a value by key in a HashMap is O(1) on average.
Answers
- True - HashMap requires Hash and Eq traits for keys
- False - HashMap does not maintain insertion order (use IndexMap for that)
- False - f64 doesn't implement Hash due to NaN issues; use OrderedFloat
- True - The entry() API is designed for efficient insert-or-update patterns
- False - get() returns Option<&V>, not V directly
- True - HashMap lookup is O(1) average case
Find the Bug
Question 1:
#![allow(unused)] fn main() { use std::collections::HashMap; fn count_words<'a>(words: &[&'a str]) -> HashMap<&'a str, i32> { let mut counts = HashMap::new(); for word in words { counts.entry(word).or_insert(0) += 1; } counts } }
Answer
Bug: We need to dereference the key word to get the &str, not the &&str
and then dereference counts... so we can modify the value.
Fix:
#![allow(unused)] fn main() { use std::collections::HashMap; fn count_words<'a>(words: &[&'a str]) -> HashMap<&'a str, i32> { let mut counts = HashMap::new(); for word in words { *counts.entry(*word).or_insert(0) += 1; } counts } }
Question 2:
#![allow(unused)] fn main() { use std::collections::HashMap; fn merge_maps(map1: HashMap<String, i32>, map2: HashMap<String, i32>) -> HashMap<String, i32> { let mut result = map1; for (key, value) in map2 { result.insert(key, result.get(&key).unwrap() + value); } result } }
Answer
Bug: Using get(&key).unwrap() on a key that might not exist in result (map1). If a key from map2 is not in map1, this panics.
Fix:
#![allow(unused)] fn main() { use std::collections::HashMap; fn merge_maps(map1: HashMap<String, i32>, map2: HashMap<String, i32>) -> HashMap<String, i32> { let mut result = map1; for (key, value) in map2 { *result.entry(key).or_insert(0) += value; } result } }
Predict the Output
Question 1:
use std::collections::HashMap; fn main() { let mut scores = HashMap::new(); scores.insert("Alice", 85); scores.insert("Bob", 90); scores.insert("Alice", 95); let alice_score = scores.get("Alice").unwrap_or(&0); let carol_score = scores.get("Carol").unwrap_or(&0); println!("{} {}", alice_score, carol_score); }
Answer
Output: 95 0
Reasoning:
- "Alice" is inserted twice. The second insert (95) overwrites the first (85).
get("Alice")returns Some(&95), unwrap_or gives 95get("Carol")returns None, unwrap_or provides default &0
Question 2:
use std::collections::HashMap; fn main() { let mut map: HashMap<&str, i32> = HashMap::new(); *map.entry("a").or_insert(0) += 1; *map.entry("b").or_insert(5) += 1; *map.entry("a").or_insert(10) += 1; let a = map.get("a").unwrap(); let b = map.get("b").unwrap(); println!("{} {}", a, b); }
Answer
Output: 2 6
Reasoning:
- First entry("a"): key doesn't exist, inserts 0, then += 1 → a = 1
- entry("b"): key doesn't exist, inserts 5, then += 1 → b = 6
- Second entry("a"): key exists (value 1), or_insert(10) does NOT insert, just returns &mut to existing value, then += 1 → a = 2
Coding Challenge
Challenge: Implement most_frequent
Write a function that takes a slice of integers and returns the value that appears most frequently. Return None if the slice is empty.
use std::collections::HashMap; fn most_frequent(numbers: &[i32]) -> Option<i32> { // Your code here } fn main() { let nums = vec![1, 2, 2, 3, 3, 3, 4]; println!("{:?}", most_frequent(&nums)); // Should print Some(3) println!("{:?}", most_frequent(&[])); // Should print None }
2. HashSet and Set Operations
Quick Review
HashSet
- Elements must implement
HashandEqtraits - O(1) average lookup, insertion, deletion
- Automatically removes duplicates
- Does NOT maintain insertion order
Key Methods:
insert(value)- returnsbool(true if new)contains(&value)- returnsbool(true if value is in the set)remove(&value)- returnsbool(true if value was in the set)
Set Operations:
intersection(&other)- returns a set with elements in both setsunion(&other)- returns a set with elements in either setdifference(&other)- returns a set with elements in self but not othersymmetric_difference(&other)- returns a set with elements in one but not both
Examples
#![allow(unused)] fn main() { use std::collections::HashSet; // Creating HashSets let set1: HashSet<i32> = vec![1, 2, 3, 4].into_iter().collect(); let set2: HashSet<i32> = vec![3, 4, 5, 6].into_iter().collect(); // Set operations let inter: HashSet<_> = set1.intersection(&set2).copied().collect(); println!("inter: {:?}", inter); // inter = {3, 4} let uni: HashSet<_> = set1.union(&set2).copied().collect(); println!("uni: {:?}", uni); // uni = {1, 2, 3, 4, 5, 6} let diff: HashSet<_> = set1.difference(&set2).copied().collect(); println!("diff: {:?}", diff); // diff = {1, 2} (in set1 but not set2) let sym_diff: HashSet<_> = set1.symmetric_difference(&set2).copied().collect(); println!("sym_diff: {:?}", sym_diff); // sym_diff = {1, 2, 5, 6} // Checking membership let has_three = set1.contains(&3); // true println!("has_three: {}", has_three); // HashSet for uniqueness let words = vec!["apple", "banana", "apple", "cherry"]; let unique: HashSet<_> = words.into_iter().collect(); println!("unique: {:?}", unique); // unique = {"apple", "banana", "cherry"} }
True/False Questions
-
T/F: HashSet automatically removes duplicate values.
-
T/F: Elements in a HashSet must implement
HashandEqtraits. -
T/F: HashSet maintains elements in sorted order.
-
T/F: The
intersection()method returns elements common to two sets. -
T/F: Checking if an element exists in a HashSet is O(n).
Find the Bug
Question:
#![allow(unused)] fn main() { use std::collections::HashSet; fn find_common<T: PartialEq>(set1: &HashSet<T>, set2: &HashSet<T>) -> HashSet<T> { set1.intersection(set2).cloned().collect() } }
Predict the Output
Question:
use std::collections::HashSet; fn main() { let set1: HashSet<&str> = vec!["apple", "banana", "cherry"].into_iter().collect(); let set2: HashSet<&str> = vec!["cherry", "date", "elderberry"].into_iter().collect(); let inter: HashSet<_> = set1.intersection(&set2).copied().collect(); println!("inter: {:?}", inter); let diff: HashSet<_> = set1.difference(&set2).copied().collect(); println!("diff: {:?}", diff); let sym_diff: HashSet<_> = set1.symmetric_difference(&set2).copied().collect(); println!("sym_diff: {:?}", sym_diff); println!("{} {} {}", inter.len(), diff.len(), sym_diff.len()); }
Coding Challenge
Challenge: Find duplicates
Write a function that takes a slice of integers and returns a Vec containing only the values that appear more than once. The result should not contain duplicates itself.
use std::collections::{HashMap, HashSet}; fn find_duplicates(numbers: &[i32]) -> Vec<i32> { // Your code here } fn main() { let nums = vec![1, 2, 2, 3, 3, 3, 4, 5, 5]; println!("{:?}", find_duplicates(&nums)); // [2, 3, 5] (order may vary) }
3. BTreeMap and Ordered Collections
Quick Review
BTreeMap<K, V> is a sorted map based on B-trees:
- Keys are always in sorted order
- Keys must implement
Ordtrait (not Hash) - O(log n) lookup, insertion, deletion
- Efficient for range queries
- Iteration yields key-value pairs in sorted key order
When to use BTreeMap vs HashMap:
- HashMap: faster single-key operations (O(1) vs O(log n))
- BTreeMap: need sorted order, range queries, or keys don't implement Hash
Examples
#![allow(unused)] fn main() { use std::collections::BTreeMap; let mut map = BTreeMap::new(); map.insert(3, "three"); map.insert(1, "one"); map.insert(4, "four"); // Iteration is in sorted key order for (k, v) in map.iter() { println!("{}: {}", k, v); } // Output: // 1: one // 3: three // 4: four // First and last keys let first = map.keys().next(); // Some(&1) let last = map.keys().last(); // Some(&4) // Range queries for (k, v) in map.range(2..=4) { println!("{}: {}", k, v); } // Output: 3: three, 4: four }
True/False Questions
-
T/F: BTreeMap stores keys in sorted order.
-
T/F: Insertion into a BTreeMap is O(1).
-
T/F: BTreeMap requires keys to implement the
Hashtrait. -
T/F: Iterating over a BTreeMap yields key-value pairs in sorted key order.
-
T/F: BTreeMap is faster than HashMap for all operations.
Predict the Output
Question:
use std::collections::BTreeMap; fn main() { let mut scores = BTreeMap::new(); scores.insert("Charlie", 85); scores.insert("Alice", 95); scores.insert("Bob", 90); let first_key = scores.keys().next().unwrap(); let last_key = scores.keys().last().unwrap(); println!("{} {}", first_key, last_key); }
4. VecDeque and Circular Buffers
Quick Review
VecDeque
- O(1) push/pop from both ends
- Implemented as a circular/ring buffer
- Can be used as a stack OR a queue
- Grows dynamically like Vec
Key Methods:
push_front(value)- add to frontpush_back(value)- add to backpop_front()- remove from front, returnsOption<T>pop_back()- remove from back, returnsOption<T>front()/back()- peek without removing
Use Cases:
- Queue (FIFO): push_back + pop_front
- Stack (LIFO): push_back + pop_back
- Rolling windows / circular buffers
Examples
#![allow(unused)] fn main() { use std::collections::VecDeque; let mut deque: VecDeque<i32> = VecDeque::new(); // Building a deque deque.push_back(1); // [1] deque.push_back(2); // [1, 2] deque.push_front(3); // [3, 1, 2] deque.push_back(4); // [3, 1, 2, 4] // Removing elements let front = deque.pop_front(); // Some(3), deque is [1, 2, 4] let back = deque.pop_back(); // Some(4), deque is [1, 2] // Using as a queue (FIFO) let mut queue = VecDeque::new(); queue.push_back("first"); queue.push_back("second"); let next = queue.pop_front(); // Some("first") // Iteration for val in deque.iter() { println!("{}", val); } }
True/False Questions
-
T/F: VecDeque allows efficient O(1) insertion and removal at both ends.
-
T/F: VecDeque is implemented as a circular buffer.
-
T/F: VecDeque can only store elements that implement
Copy. -
T/F:
push_front()andpush_back()are the primary insertion methods. -
T/F: VecDeque maintains elements in sorted order.
-
T/F:
VecDeque::push_front()is O(n).
Predict the Output
Question 1:
use std::collections::VecDeque; fn main() { let mut buffer: VecDeque<i32> = VecDeque::new(); buffer.push_back(1); buffer.push_back(2); buffer.push_front(3); buffer.push_back(4); buffer.pop_front(); let sum: i32 = buffer.iter().sum(); println!("{}", sum); }
Question 2:
use std::collections::VecDeque; fn main() { let mut q: VecDeque<i32> = VecDeque::new(); q.push_back(10); q.push_back(20); q.push_back(30); let first = q.pop_front().unwrap(); q.push_back(first + 5); for val in q.iter() { print!("{} ", val); } println!(); }
Coding Challenge
Challenge: Implement a Rolling Average
Write a function that calculates the running (cumulative) average at each position. The running average at position i is the mean of all elements from index 0 to i.
fn running_average(values: &[f64]) -> Vec<f64> { // Your code here } fn main() { let data = vec![2.0, 4.0, 6.0, 8.0]; let result = running_average(&data); println!("{:?}", result); // Should print [2.0, 3.0, 4.0, 5.0] }
5. Iterators and Iterator Chains
Quick Review
Iterator Creation:
iter()- yields&T(immutable references)iter_mut()- yields&mut T(mutable references)into_iter()- consumes collection, yields ownedT
Key Iterator Methods:
map(|x| ...)- transform each elementfilter(|x| ...)- keep elements matching predicatefold(init, |acc, x| ...)- accumulate into single valuecollect()- consume iterator into collectionsum()- sum all elementscount()- count elementstake(n)- take first n elementsskip(n)- skip first n elementsenumerate()- yields(index, value)pairs
Important: Iterator adaptors (map, filter, etc.) are lazy - they don't execute until consumed by a method like collect(), sum(), or for loop.
Examples
#![allow(unused)] fn main() { let numbers = vec![1, 2, 3, 4, 5, 6]; // Filter and map let result: Vec<i32> = numbers.iter() .filter(|&&x| x % 2 == 0) // keep even: [2, 4, 6] .map(|x| x * 2) // double: [4, 8, 12] .collect(); // Sum let sum: i32 = numbers.iter().sum(); // 21 // Filter, map, take let result: Vec<i32> = numbers.iter() .filter(|&&x| x % 2 == 1) // keep odd: [1, 3, 5] .map(|x| x * x) // square: [1, 9, 25] .take(2) // first 2: [1, 9] .collect(); // Enumerate for (i, val) in numbers.iter().enumerate() { println!("Index {}: {}", i, val); } // Fold for custom accumulation let product: i32 = numbers.iter() .fold(1, |acc, x| acc * x); // 720 }
True/False Questions
-
T/F: Iterator methods like
map()andfilter()are lazily evaluated. -
T/F: The
collect()method transforms an iterator into a collection. -
T/F: Calling
.iter()on a Vec transfers ownership of the elements. -
T/F: The
fold()method requires an initial accumulator value. -
T/F: Iterator chains are evaluated from right to left.
Find the Bug
Question 1:
#![allow(unused)] fn main() { fn double_evens(numbers: &[i32]) -> Vec<i32> { numbers.iter() .filter(|&x| x % 2 == 0) .map(|x| x * 2) } }
Question 2:
#![allow(unused)] fn main() { fn sum_positive(numbers: &[i32]) -> i32 { numbers.iter() .filter(|x| x > 0) .sum() } }
Predict the Output
Question 1:
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6]; let result: Vec<i32> = numbers.iter() .filter(|&x| x % 2 == 1) .map(|x| x * x) .take(2) .collect(); println!("{:?}", result); }
Question 2:
fn main() { let data = vec![10, 20, 30, 40, 50]; let result: i32 = data.iter() .skip(1) .take(3) .filter(|&&x| x > 25) .sum(); println!("{}", result); }
Question 3:
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let sum: i32 = numbers.iter() .enumerate() .filter(|(i, _)| i % 2 == 0) .map(|(_, v)| v) .sum(); println!("{}", sum); }
Coding Challenge
Challenge: Count elements in range
Write a function that counts how many elements in a slice fall within a given range [low, high] (inclusive).
fn count_in_range(numbers: &[i32], low: i32, high: i32) -> usize { // Your code here - use iterator methods } fn main() { let nums = vec![1, 5, 10, 15, 20, 25]; println!("{}", count_in_range(&nums, 5, 20)); // Should print 4 }
6. Algorithm Complexity
Quick Review
Big O Notation describes how runtime grows with input size:
| Complexity | Name | Example |
|---|---|---|
| O(1) | Constant | HashMap lookup, Vec::push (amortized) |
| O(log n) | Logarithmic | BTreeMap operations, binary search |
| O(n) | Linear | Linear search, single loop |
| O(n log n) | Linearithmic | Sorting (merge sort, quicksort) |
| O(n²) | Quadratic | Nested loops, bubble sort |
Common Operations:
| Data Structure | Insert | Lookup | Delete |
|---|---|---|---|
| Vec (end) | O(1)* | O(1) | O(1) |
| Vec (middle) | O(n) | O(1) | O(n) |
| HashMap | O(1) | O(1) | O(1) |
| BTreeMap | O(log n) | O(log n) | O(log n) |
| VecDeque (ends) | O(1) | O(1) | O(1) |
*amortized
Graph Algorithms:
- BFS (Breadth-First Search): uses a queue (FIFO)
- DFS (Depth-First Search): uses a stack (LIFO)
True/False Questions
-
T/F: A
Vec::push()operation is O(1) amortized. -
T/F: Searching for a key in a HashMap is O(n) in the average case.
-
T/F: Sorting a vector with
.sort()is O(n log n). -
T/F: Graph BFS traversal uses a queue data structure.
-
T/F: Inserting into a BTreeMap is O(1).
7. Option and Result Types
Quick Review
Option
Some(value)- contains a valueNone- no value
Result<T, E> - for operations that might fail:
Ok(value)- success with valueErr(error)- failure with error
Common Methods:
unwrap()- get value or panicunwrap_or(default)- get value or defaultunwrap_or_else(|| ...)- get value or compute default?operator - propagate errors (Result) or None (Option)is_some()/is_ok()- check variantmap(|x| ...)- transform if Some/Ok
Examples
#![allow(unused)] fn main() { // Option let maybe_value: Option<i32> = Some(5); let no_value: Option<i32> = None; let x = maybe_value.unwrap_or(0); // 5 let y = no_value.unwrap_or(0); // 0 // Result fn divide(a: i32, b: i32) -> Result<i32, String> { if b == 0 { Err(String::from("division by zero")) } else { Ok(a / b) } } let result = divide(10, 2); // Ok(5) let error = divide(10, 0); // Err("division by zero") // Using ? to propagate fn calculate(a: i32, b: i32) -> Result<i32, String> { let quotient = divide(a, b)?; // Returns Err early if divide fails Ok(quotient * 2) } }
True/False Questions
-
T/F:
Option::unwrap()will panic if the value isNone. -
T/F: The
?operator can be used to propagate errors fromResult. -
T/F:
Some(5)andNoneare both variants ofOption<i32>. -
T/F:
Result<T, E>is used for operations that might fail with an error. -
T/F:
unwrap_or(default)returns the contained value or a provided default.
Final Tips for the Exam
-
HashMap vs BTreeMap: Use HashMap for fast O(1) lookups. Use BTreeMap when you need sorted keys or range queries.
-
Entry API: Always use
entry().or_insert()for counting patterns instead ofget().unwrap(). -
HashSet trait bounds: Remember that HashSet requires
Hash + Eq, not justPartialEq. -
Iterator laziness: Remember to call
.collect()or another consumer - map/filter alone don't execute! -
Reference patterns in closures:
iter()yields&Tfilter(|x| ...)receives&&Twhen used with iter()- Use
|&x|or|&&x|to destructure
-
VecDeque for both ends: Use VecDeque when you need efficient push/pop from both front and back.
-
Complexity matters: Know that HashMap is O(1), BTreeMap is O(log n), and sorting is O(n log n).
-
Understand references and when to dereference: Remember that iterators yield references, not values.
-
Review the preliminaries as well!
Good luck on your final exam! 🦀