Advent of Code 2025: Day 6
Problem
tl;dr
You have a math worksheet where problems are arranged vertically in columns:
123 328 51 64
45 64 387 23
6 98 215 314
* + * +
- Numbers in the same problem are stacked vertically
- The operation (
+or*) is at the bottom - Problems are separated by columns of only spaces
Part 1: Read numbers horizontally (row by row). Solve each problem, then sum all answers.
| Problem | Calculation | Answer |
|---|---|---|
| 123, 45, 6 | 123 × 45 × 6 | 33210 |
| 328, 64, 98 | 328 + 64 + 98 | 490 |
| 51, 387, 215 | 51 × 387 × 215 | 4243455 |
| 64, 23, 314 | 64 + 23 + 314 | 401 |
Part 1 total: 33210 + 490 + 4243455 + 401 = 4277556
Part 2: Plot twist — cephalopod math is read right-to-left, and each column is a digit (top = most significant, bottom = least significant).
Same input, completely different parsing:
123 328 51 64
45 64 387 23
6 98 215 314
* + * +
Reading right-to-left, column by column:
| Problem (R→L) | Numbers | Calculation | Answer |
|---|---|---|---|
| Rightmost | 4, 431, 623 | 4 + 431 + 623 | 1058 |
| 2nd from right | 175, 581, 32 | 175 × 581 × 32 | 3253600 |
| 3rd from right | 8, 248, 369 | 8 + 248 + 369 | 625 |
| Leftmost | 356, 24, 1 | 356 × 24 × 1 | 8544 |
Part 2 total: 1058 + 3253600 + 625 + 8544 = 3263827
Key insight: Part 1 reads rows to get numbers. Part 2 reads columns to construct each digit of a number (top-to-bottom = MSD-to-LSD), then processes problems right-to-left.
My Solution (Handcrafted)
This felt like a data engineering problem — parsing columnar data with weird formatting. Right up my alley.
Part 1: Split and Slice
The trick is that split_whitespace does all the heavy lifting. Each row becomes a clean list of numbers/operators:
pub fn solve_1(filename: &str) -> u64 {
let input = Self::read_input(filename);
let parsed_input: Vec<Vec<String>> = input
.iter()
.map(|line| line.split_whitespace().map(|s| s.to_string()).collect())
.collect();
let mut total = 0;
for i in 0..parsed_input[0].len() {
let operator = parsed_input[parsed_input.len() - 1][i].clone();
let mut sequence = vec![];
for j in 0..parsed_input.len() - 1 {
sequence.push(parsed_input[j][i].parse::<u64>().unwrap());
}
match operator.as_str() {
"+" => total += sequence.iter().sum::<u64>(),
"*" => total += sequence.iter().product::<u64>(),
_ => panic!("Invalid operator: {}", operator),
}
}
total
}
Each column index i gives us one problem. Grab the operator from the last row, collect numbers from all other rows at that index, apply the operation.
Part 2: Character-by-Character Parsing
Part 2 is trickier — we can’t use split_whitespace because spacing matters now. Each character position is a potential digit.
I use the operator row to find where each problem starts and ends:
pub fn solve_2(filename: &str) -> u64 {
let input = Self::read_input(filename);
let mut total = 0;
let mut column_start: usize = 0;
let mut column_end: usize = 1;
loop {
// use the operator row to find the width of a column
loop {
if column_end == input[0].len() {
break;
}
match input[input.len() - 1].chars().nth(column_end).unwrap() {
' ' => column_end += 1,
_ => break,
}
}
let operator = input[input.len() - 1].chars().nth(column_start).unwrap();
// build the sequence of numbers with digits
let mut sequence = vec![];
for i in column_start..column_end {
let mut number_string = String::new();
for j in 0..input.len() - 1 {
let digit = input[j].chars().nth(i).unwrap();
number_string.push(digit);
}
number_string = number_string.replace(" ", "");
if number_string.is_empty() {
continue;
}
let number = number_string.parse::<u64>().unwrap();
sequence.push(number);
}
match operator {
'+' => total += sequence.iter().sum::<u64>(),
'*' => total += sequence.iter().product::<u64>(),
_ => panic!("Invalid operator: {}", operator),
}
// ... advance to next problem
}
total
}
For each character column within a problem, read top-to-bottom to get digits, concatenate them into a number string, then parse.
Gemini’s Solution
Gemini took a more structured approach with helper functions.
Grid Preprocessing
First, pad all lines to the same length to make column access easier:
fn read_padded_grid(filename: &str) -> Vec<Vec<char>> {
let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
let max_len = lines.iter().map(|s| s.len()).max().unwrap_or(0);
lines
.iter()
.map(|s| {
let mut chars: Vec<char> = s.chars().collect();
while chars.len() < max_len {
chars.push(' ');
}
chars
})
.collect()
}
Problem Detection
Uses a helper to check if a column is all spaces (problem separator):
fn is_col_empty(grid: &Vec<Vec<char>>, col: usize) -> bool {
for row in grid {
if col < row.len() && !row[col].is_whitespace() {
return false;
}
}
true
}
Part 1: Block Processing
For each problem block, extract numbers from rows:
fn process_block_part1(grid: &Vec<Vec<char>>, start: usize, end: usize) -> u64 {
let mut numbers: Vec<u64> = Vec::new();
let mut operator = None;
for row in grid {
let slice = &row[start..std::cmp::min(end, row.len())];
let s: String = slice.iter().collect();
let trimmed = s.trim();
if trimmed == "+" {
operator = Some('+');
} else if trimmed == "*" {
operator = Some('*');
} else if let Ok(n) = trimmed.parse::<u64>() {
numbers.push(n);
}
}
match operator {
Some('+') => numbers.iter().sum(),
Some('*') => numbers.iter().product(),
_ => 0,
}
}
Part 2: Vertical Column Reading
Same structure, but read columns instead of rows:
fn process_block_part2(grid: &Vec<Vec<char>>, start: usize, end: usize) -> u64 {
let mut numbers: Vec<u64> = Vec::new();
let mut operator = None;
for col in start..end {
let mut num_str = String::new();
for row in grid {
let c = row[col];
if c.is_ascii_digit() {
num_str.push(c);
} else if c == '+' {
operator = Some('+');
} else if c == '*' {
operator = Some('*');
}
}
if !num_str.is_empty() {
if let Ok(n) = num_str.parse::<u64>() {
numbers.push(n);
}
}
}
match operator {
Some('+') => numbers.iter().sum(),
Some('*') => numbers.iter().product(),
_ => 0,
}
}
Comparison
| Aspect | My Solution | Gemini’s Solution |
|---|---|---|
| Part 1 | split_whitespace — simple | Slice grid blocks — more explicit |
| Part 2 | Operator row to find boundaries | is_col_empty helper |
| Grid handling | Direct string indexing | Padded char grid |
| Structure | Inline logic in solve functions | Helper functions for each step |
| Code length | ~90 lines | ~170 lines |
Reflection
Both solutions work, different styles.
My approach relies on split_whitespace doing the parsing work for Part 1, which keeps it compact. For Part 2, I use the operator row as a guide to find problem boundaries — simple but requires careful index tracking.
Gemini’s approach is more defensive — pad the grid first, use helper functions to detect empty columns, process blocks with dedicated functions. More lines of code but easier to debug if something goes wrong.
For a parsing problem like this, both approaches are valid. Mine is more “data engineer” style — trust the structure, slice and dice. Gemini’s is more “software engineer” style — abstract into helpers, handle edge cases explicitly.
Time spent: ~15 minutes