Day 3: Solution Explanation
Approach
Day 3's problem involves finding common items across different sets and calculating their priorities. We need to:
- Part 1: Find items that appear in both compartments of each rucksack
- Part 2: Find the common item (badge) among each group of three elves
The key techniques we'll use are:
- String splitting to divide rucksacks into compartments
- HashSets for efficiently finding common elements
- Character mapping to calculate priorities
Implementation Details
Part 1: Finding Common Items in Compartments
The approach for Part 1 is:
- Split each rucksack into two equal compartments
- Find the characters that appear in both compartments
- Calculate the priority of each common character
- Sum the priorities
#![allow(unused)] fn main() { fn component_1(lines: &str) -> u32 { lines.lines() .map(|line| line.split_at(line.len()>>1)) .map(|(compa, compb)| { compa.chars() .filter(|&c| compb.find(c).is_some()) .collect::<HashSet<_>>() }) .map(|set| set.into_iter().map(calculate_priority).sum::<u32>()) .reduce(|sum, v| sum + v) .unwrap_or_else(|| unreachable!()) } }
Key points about this implementation:
line.split_at(line.len()>>1)
divides the string into two equal halvescompb.find(c).is_some()
checks if characterc
appears in the second compartment- We use a
HashSet
to ensure we count each common character only once
Part 2: Finding Common Items Across Groups
The approach for Part 2 is:
- Group the rucksacks into sets of three
- For each group, find the characters that appear in all three rucksacks
- Calculate the priority of each common character
- Sum the priorities
#![allow(unused)] fn main() { fn component_2(lines:&str) -> u32 { lines.lines() .collect::<Vec<_>>() .chunks(3) .map(|group| { group.iter() .map(|a| a.chars().collect::<HashSet<_>>()) .reduce(|a, b| a.intersection(&b).copied().collect::<HashSet<_>>()) .unwrap_or_else(|| panic!("Ops!")) }) .map(|set| set.into_iter().map(calculate_priority).sum::<u32>()) .sum::<u32>() } }
Key points about this implementation:
.chunks(3)
splits the lines into groups of three- We convert each rucksack into a
HashSet
of characters - We use
reduce
withintersection
to find characters common to all three rucksacks
Priority Calculation
Both parts use the same logic to calculate priorities:
#![allow(unused)] fn main() { fn calculate_priority(c: char) -> u32 { match c { 'a'..='z' => u32::from(c) - u32::from('a') + 1, // 1-26 'A'..='Z' => u32::from(c) - u32::from('A') + 27, // 27-52 _ => panic!("use only alphabetic characters") } } }
This function:
- Maps lowercase letters (a-z) to priorities 1-26
- Maps uppercase letters (A-Z) to priorities 27-52
Optimization and Efficiency
Time Complexity
- Part 1: O(n) where n is the total number of characters across all rucksacks
- Part 2: O(n) where n is the total number of characters across all rucksacks
The solution makes use of HashSets for efficient intersection operations.
Space Complexity
- O(m) where m is the number of unique characters in the largest compartment/rucksack
Alternative Approaches
Bitsets for Character Tracking
An alternative approach could use bitsets to track character presence:
#![allow(unused)] fn main() { fn using_bitsets(lines: &str) -> u32 { lines.lines() .map(|line| { let half_len = line.len() / 2; let first_half = &line[0..half_len]; let second_half = &line[half_len..]; let mut first_set = 0u64; let mut second_set = 0u64; for c in first_half.chars() { let bit = if c.is_lowercase() { 1u64 << (c as u8 - b'a') } else { 1u64 << (c as u8 - b'A' + 26) }; first_set |= bit; } for c in second_half.chars() { let bit = if c.is_lowercase() { 1u64 << (c as u8 - b'a') } else { 1u64 << (c as u8 - b'A' + 26) }; second_set |= bit; } let common = first_set & second_set; common.trailing_zeros() + 1 }) .sum() } }
This approach would be more memory-efficient but slightly more complex to implement.
Conclusion
The solution uses Rust's powerful iterators and collection types to create a clean, functional implementation. The use of HashSets makes finding common elements efficient, while the string manipulation functions allow for straightforward parsing of the input.