Day 6: Trash Compactor
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
Uiua
I’m new to Uiua, so probably not the best way to express the solution!
ParseRows ← ( ⊜∘⊸≠@\n ⍜⇌°⊂ # Get the row of operations. ) ParseOperations ← ⊜⊢⊸≠@\s SumCalculations ← ( ≡( ⍣(◇/×°@* | ◇/+°@+ ) ) /+ ) Part₁ ← ( ParseRows ⊓(ParseOperations | ⍉ ≡(⊜⋕⊸≠@\s) # Parse columns. ) SumCalculations ) Part₂ ← ( ParseRows ⊓(ParseOperations | ⊜(□≡(⋕▽⊸≠@\s)) ≡/↥⊸≠@\s⍉ # Parse numbers. ) SumCalculations ) &fras "6.txt" ⊃Part₁ Part₂
( ͡° ͜ʖ├┬┴┬┴
Looks good. I like how you managed to use the same structure for Parts 1 and 2; that’s more than I did. And yours is faster than mine too.
One of us! One of us!
Nice use of the inversion, I always forget that’s a thing
nushell
I was afk when the puzzle went up so I had another go at doing it on my phone in Turmux with my shell’s scripting language. It’s quite nice how your shell is also a REPL so you can build up the answer in pieces, although I wrote a file for the second part.

open input.txt | str replace --all --regex ' +' ' ' | lines | each { $in | str trim } | to text | from csv --noheaders --separator ' ' | reverse | transpose --ignore-titles | each { |list| transpose | skip 1 | if $list.column0 == '+' { math sum } else { math product } } | math sumPart 2
let input = open input.txt | lines | each { $in | split chars } let last_row = ($input | length) - 1 let last_col = ($input | first | length) - 1 mut op = ' ' mut numbers = [] mut grand_tot = 0 for x in $last_col..0 { if $op == '=' { $op = ' ' continue } let n = 0..($last_row - 1) | each { |y| $input | get $y | get $x } | str join | into int $numbers = ($numbers | append $n) $op = $input | get $last_row | get $x if $op != ' ' { $grand_tot += $numbers | if $op == '+' { math sum } else { math product } $numbers = [] $op = '=' } } $grand_totHaskell
There’s probably a really clever way of abstracting just the difference between the two layouts.
import Data.Char (isSpace) import Data.List (transpose) import Data.List.Split (splitWhen) op '+' = sum op '*' = product part1 = sum . map ((op . head . last) <*> (map read . init)) . (transpose . map words . lines) part2 = sum . map ((op . last . last) <*> map (read . init)) . (splitWhen (all isSpace) . reverse . transpose . lines) main = do input <- readFile "input06" print $ part1 input print $ part2 inputUlua probably has a single character that rotates the input -90 degrees…
Your code really reads like your explaining the solution out loud. That’s so elegant!
Thanks! I try to write code to be readable by humans above all else.
Uiua
This was fun :D
I had a fun experience just throwing the strings with both numbers and spaces at the parse function. In the online pad, everything worked out fine but running the same code on my input locally gave me a “invalid float literal” error.
I thought I’d missed some edge case in the real input again, like is often the case.
Turns out that the Uiua version I used locally had a bug that’s fixed in the latest build. For once it wasn’t directly my fault ^^Code
$ 123 328 51 64 $ 45 64 387 23 $ 6 98 215 314 $ * + * + # &fras "input-6.txt" ◌ Calc ← ( ↘₂⊛⊂"+*"/◇⊂ ≡◇⨬(/+|/×) /+ ) P₁ ← ( ⊜(⊜□⊸≠@ )⊸≠@\n ⊃⊣↘₋₁ ⊙(⍉≡₀◇⋕) Calc ) P₂ ← ( ⊜∘⊸≠@\n ⟜⧻ ⊓⍉(˜↯@ ) ⊜(⊙(□≡⋕)⍜⍉(⊃(⊢⊣)↘₋₁))¬⤚≡⌟≍ Calc ) 1_2 [⊃P₁P₂] ≡(&p &pf $"Part _: ")Ironically for Lisp, a good chunk of the work here is type conversion, because strings, vectors, multidimensional arrays, characters, and numbers don’t have implicit conversions between them; you have to specify what you want explicitly. I also found it easier to manually transpose the character array for part 2 rather than traverse in column-major order, because that makes the relationship between input and output data structure more transparent.
(ql:quickload :str) (ql:quickload :array-operations) (defun parse-line-1 (line) (let ((broken-line (str:split " " (str:collapse-whitespaces (str:trim line))))) (mapcar #'(lambda (s) (cond ((equal s "+") #'+) ((equal s "*") #'*) (t (parse-integer s)))) broken-line))) (defun read-inputs-1 (filename) (let* ((input-lines (uiop:read-file-lines filename))) (mapcar #'parse-line-1 input-lines))) (defun main-1 (filename) (let* ((problems (read-inputs-1 filename)) (arguments (apply #'mapcar #'list (butlast problems)))) (reduce #'+ (mapcar #'apply (car (last problems)) arguments)))) (defun parse-operands-2 (lines) (let* ((initial-rows (length lines)) (initial-cols (length (car lines))) (flat-chars (make-array (list (* initial-rows initial-cols)) :initial-contents (apply #'concatenate 'string lines))) (box-chars (make-array (list initial-rows initial-cols) :displaced-to flat-chars)) (transposed-chars (aops:each-index (i j) (aref box-chars j i)))) (loop for cv across (aops:split transposed-chars 1) for s = (str:trim (coerce cv 'string)) collect (if (zerop (length s)) nil (parse-integer s))))) (defun list-split (xs sep &optional (predicate #'equal)) (let ((current nil) (result nil)) (loop for x in xs do (if (funcall predicate x sep) (progn (setf result (cons (reverse current) result)) (setf current nil)) (setf current (cons x current))) finally (setf result (cons (reverse current) result))) (reverse result))) (defun main-2 (filename) (let* ((lines (uiop:read-file-lines filename)) (operators (parse-line-1 (car (last lines)))) (operands (parse-operands-2 (butlast lines)))) (loop for rator in operators for rands in (list-split operands nil) sum (apply rator rands))))Haskell
import Control.Arrow import Data.Char import Data.List import Text.ParserCombinators.ReadP op "*" = product op "+" = sum part1 s = sum $ zipWith ($) (op <$> a) (transpose $ fmap read <$> as) where (a : as) = reverse . fmap words . lines $ s parseGroups = fst . last . readP_to_S (sepBy (endBy int eol) eol) . filter (/= ' ') where eol = char '\n' int = read <$> munch1 isDigit :: ReadP Int part2 s = sum $ zipWith ($) (op <$> words a) (parseGroups . unlines $ reverse <$> transpose as) where (a : as) = reverse $ lines s main = getContents >>= print . (part1 &&& part2)Kotlin
I’m not fully happy with my parsing today, but oh well. I also thought about just plain building the grid and then rotating it, but “normal input parsing” works too.
Solution
class Day06 : Puzzle { val numsPartOne = mutableListOf<MutableList<Long>>() val numsPartTwo = mutableListOf<List<Long>>() val ops = mutableListOf<(Long, Long) -> Long>() override fun readFile() { val input = readInputFromFile("src/main/resources/a2025/day06.txt") val lines = input.lines().filter { it.isNotBlank() } // parse part1 input for (line in lines.dropLast(1)) { for ((c, num) in line.trim().split(" +".toRegex()).withIndex()) { if (numsPartOne.getOrNull(c) == null) numsPartOne.add(mutableListOf()) numsPartOne[c].add(num.toLong()) } } // parse part2 input var numList = mutableListOf<Long>() for (c in 0..<lines.maxOf { it.length }) { var numStr = "" for (r in 0..<lines.size - 1) { numStr += lines[r].getOrElse(c) { ' ' } } if (numStr.isBlank()) { numsPartTwo.add(numList) numList = mutableListOf() } else { numList.add(numStr.trim().toLong()) } } numsPartTwo.add(numList) // parse operators ops.addAll( lines.last().split(" +".toRegex()) .map { it.trim()[0] } .map { when (it) { '*' -> { a: Long, b: Long -> a * b } '+' -> { a: Long, b: Long -> a + b } else -> throw IllegalArgumentException("Unknown operator: $it") } } ) } override fun solvePartOne(): String { return numsPartOne.mapIndexed { c, list -> list.reduce { a, b -> ops[c](a, b) } }.sum().toString() } override fun solvePartTwo(): String { return numsPartTwo.mapIndexed { c, list -> list.reduce { a, b -> ops[c](a, b) } }.sum().toString() } }full code on Codeberg
I also thought about trying to rotate, but not for very long. Mine would be a bit simpler if I’d done what you did and build the number string and then check if it’s blank.
fun main() { val input = getInput(6) val output = parseInput2(input) var total = 0L for ((numbers, operator) in output) { when (operator) { '+' -> { total += numbers.sum() } '*' -> { total += numbers.reduce { acc, number -> acc * number }} } } println(getElapsedTime()) println(total) } fun parseInput2(input: String): List<Pair<List<Long>, Char>> { val rows = input.lines() .filter { it.isNotBlank() } .map { it.toCharArray() } val output: MutableList<Pair<List<Long>, Char>> = mutableListOf() val numberRowCount = rows.size - 1 var isNewProblem = true var currentNumbers: MutableList<Long> = mutableListOf() var operator = ' ' for (column in rows[0].indices) { if (!isNewProblem && isColumnEmpty(rows, column)) { isNewProblem = true output.add(currentNumbers to operator) continue } if (isNewProblem) { isNewProblem = false currentNumbers = mutableListOf() operator = rows.last()[column] } var number = "" for (row in 0..<numberRowCount) { if (rows[row][column] != ' ') { number += rows[row][column] } } currentNumbers.add(number.toLong()) } if (!isNewProblem) { output.add(currentNumbers to operator) } return output } fun isColumnEmpty(rows: List<CharArray>, column: Int): Boolean { for (i in rows.indices) { if (rows[i][column] != ' ') { return false } } return true }
Excel
Pt1 - Its a solution, but the column width on Numbers prevents a full solution
If I get p2 working, I’ll post a new link. Dont think i will have much luck though.
Go
Part 2: Read the whole input in a rune matrix. Scan it column by column, store the numbers as you go, ignoring all spaces, and store the operand when you find it. When you hit an empty column or the end, do the operation and add it to the total.
spoiler
func part2() { // file, _ := os.Open("sample.txt") file, _ := os.Open("input.txt") defer file.Close() scanner := bufio.NewScanner(file) chars := [][]rune{} for scanner.Scan() { chars = append(chars, []rune(scanner.Text())) } m := len(chars) n := len(chars[0]) var op rune nums := []int{} total := 0 for j := range n { current := []rune{} for i := range m { if chars[i][j] == '+' || chars[i][j] == '*' { op = chars[i][j] } else if chars[i][j] != ' ' { current = append(current, chars[i][j]) } } if len(current) > 0 { x, _ := strconv.Atoi(string(current)) nums = append(nums, x) } if len(current) == 0 || j == n-1 { result := 0 if op == '*' { result = 1 } for _, x := range nums { if op == '+' { result = result + x } else { result = result * x } } total += result nums = []int{} } } fmt.Println(total) }Nim
The hardest part was reading the part 2 description. I literally looked at it for minutes trying to understand where the problem numbers come from and how they’re related to the example input. But then it clicked.
The next roadblock was that my template was stripping whitespace at the end of the last line, making parsing a lot harder. I’ve replaced
strip()withstrip(chars={'\n'})to keep the trailing space intact.Runtime:
1.4 ms618 μsview code
type AOCSolution[T,U] = tuple[part1: T, part2: U] proc solve(input: string): AOCSolution[int, int] = let lines = input.splitLines() let numbers = lines[0..^2] let ops = lines[^1] block p1: let numbers = numbers.mapIt(it.splitWhiteSpace().mapIt(parseInt it)) let ops = ops.splitWhitespace() for x in 0 .. numbers[0].high: var res = numbers[0][x] for y in 1 .. numbers.high: case ops[x] of "*": res *= numbers[y][x] of "+": res += numbers[y][x] result.part1 += res block p2: var problems: seq[(char, Slice[int])] var ind = 0 while ind < ops.len: let len = ops.skipWhile({' '}, ind+1) problems.add (ops[ind], ind .. ind + len - (if ind+len < ops.high: 1 else: 0)) ind += len + 1 for (op, cols) in problems: var res = 0 for x in cols: var num = "" for y in 0 .. numbers.high: num &= numbers[y][x] if res == 0: res = parseInt num.strip else: case op of '*': res *= parseInt num.strip of '+': res += parseInt num.strip else: discard result.part2 += resFull solution at Codeberg: solution.nim
Managed to keep it compact, but boy, do I hate cephalopod math >_<
Python
from csv import reader from functools import reduce from itertools import pairwise from operator import mul from pathlib import Path from typing import Any, List, Sequence def _calc(values: List[str]) -> int: match values[-1]: case "+": return sum(map(int, values[:-1])) case "*": return reduce(mul, map(int, values[:-1])) case _: return 0 def _transpose(values: Sequence[Sequence[Any]]) -> List[List[Any]]: return [[values[row][col] for row in range(len(values))] for col in range(len(values[0]))] def part_one(input: str) -> int: def _parse_input(input: str) -> List[List[str]]: return _transpose(list(map(lambda r: list(filter(None, r)), reader(input.splitlines(), delimiter=" ")))) return sum(map(_calc, _parse_input(input))) def part_two(input: str) -> int: def _parse_input(input: str) -> List[List[str]]: data = list(input.splitlines()) columns = [t[0] for t in filter(lambda t: t[1] != " ", enumerate(data[-1]))] + [len(data[0])] numbers = [[line[a:b] for line in data[:-1]] for a, b in pairwise(columns)] numbers = [list(filter(None, ["".join(num).strip() for num in column])) for column in map(_transpose, numbers)] return list(map(lambda t: t[0] + [t[1]], zip(numbers, list(filter(None, data[-1].split(" ")))))) return sum(map(_calc, _parse_input(input))) if __name__ == "__main__": input = Path("_2025/_6/input").read_text("utf-8") print(part_one(input)) print(part_two(input))Python
For part1, regex is king. Thought about rotating the grid for part2, but going column by column is simple enough.
view code
import re from operator import add, mul def part1(data: str): # split into row, but do not split into cells yet rows = data.splitlines() m = len(rows) # number of rows # initialize the result and operator arrays # the operators array will store the operator function for each column block # the result array will store the initial value for each column block operators = [] res = [] # using regex to skip variable number of spaces for symbol in re.findall(r'\S', rows[-1]): if symbol == '+': operators.append(add) res.append(0) elif symbol == '*': operators.append(mul) res.append(1) n = len(res) # number of columns # iterate through each row, except the last one for i in range(m-1): # use regex to find all numbers in the row for j, num in enumerate(map(int, re.findall(r'\d+', rows[i]))): # apply the operator to update the result for the appropriate column res[j] = operators[j](res[j], num) return sum(res) def part2(data: str): # completely split into grid grid = [list(line) for line in data.splitlines()] m, n = len(grid), len(grid[0]) res = 0 curr = None # current value of the block op = None # operator for the block for j in range(n): if curr is None: # we just started a new block # update the current value and operator based on the symbol symbol = grid[-1][j] curr = 0 if symbol == '+' else 1 op = add if symbol == '+' else mul # read the number from the column num = 0 for i in range(m-1): if grid[i][j] != ' ': num = num * 10 + int(grid[i][j]) # if there is no number, we are at the end of a block if num == 0: # add the block value to the result # and reset the current value and operator res += curr curr = None op = None continue # otherwise, update the current value using the operator curr = op(curr, num) # finally, don't forget to add the last block value that's being tracked res += curr return res sample = """123 328 51 64 45 64 387 23 6 98 215 314 * + * + """ assert part1(sample) == 4277556 assert part2(sample) == 3263827Rust
Pt1 easy, pt2 cry
edit: Updated with more iterring :)
view code
#[test] fn test_y2025_day6_part1() { let input = include_str!("../../input/2025/day_6.txt"); let lines = input.lines().collect::<Vec<&str>>(); let nr = lines[0..4] .iter() .map(|l| { l.split_whitespace() .map(|s| s.parse::<usize>().unwrap()) .collect::<Vec<usize>>() }) .collect::<Vec<Vec<usize>>>(); let operations = lines[4].split_whitespace().collect::<Vec<&str>>(); let total = operations .iter() .enumerate() .map(|(i, op)| match *op { "*" => [0, 1, 2, 3].iter().map(|j| nr[*j][i]).product::<usize>(), "+" => [0, 1, 2, 3].iter().map(|j| nr[*j][i]).sum::<usize>(), _ => panic!("Unknown operation {}", op), }) .sum::<usize>(); assert_eq!(4412382293768, total); println!("Total: {}", total); } #[test] fn test_y2025_day6_part2() { let input = std::fs::read_to_string("input/2025/day_6.txt").unwrap(); let lines = input .lines() .map(|s| s.chars().collect::<Vec<char>>()) .collect::<Vec<Vec<char>>>(); let mut i = lines[0].len(); let mut numbers = vec![]; let mut total = 0; while i > 0 { i -= 1; let number = [0, 1, 2, 3] .iter() .filter_map(|j| lines[*j][i].to_digit(10)) .fold(0, |acc, x| acc * 10 + x); if number == 0 { continue; } numbers.push(number as usize); match lines[4][i] { '*' => { total += numbers.iter().product::<usize>(); numbers.clear(); } '+' => { total += numbers.iter().sum::<usize>(); numbers.clear(); } ' ' => {} _ => panic!("Unknown operation {}", lines[4][i]), } } assert_eq!(7858808482092, total); println!("Total: {}", total); }Why not use
.iter().sum()and.iter().product()?Because I wasn’t aware of them, thanks!
Go
Damn, I actually reeaally enjoyed this one! I didn’t expect the twist of part 2, but somehow it wasn’t that hard to manage.
Here is my modern solution:
day06.go
package main import ( "aoc/utils" "fmt" "regexp" "slices" "strconv" "strings" ) type operation int func (o operation) compute(values []int) int { switch o { case add: sum := 0 for _, val := range values { sum += val } return sum case mul: product := 1 for _, val := range values { product *= val } return product } return 0 } const ( add operation = iota mul ) var allOperationSymbols = []string{"+", "*"} func operationFromSymbol(sym string) operation { switch sym { case "+": return add case "*": return mul default: panic(fmt.Sprintf("wtf is a %s?", sym)) } } type problems struct { values [][]int operations []operation } func (p *problems) feed(column string) { last := string(column[len(column)-1]) done := false if slices.Contains(allOperationSymbols, last) { p.operations = append(p.operations, operationFromSymbol(last)) column = column[:len(column)-1] done = true } val, _ := strconv.Atoi(strings.TrimSpace(column)) idx := len(p.values) - 1 p.values[idx] = append(p.values[idx], val) if done { p.values = append(p.values, []int{}) } } func (p *problems) addLine(line string) (done bool) { parts := strings.Split(line, " ") parts = slices.DeleteFunc(parts, func(elem string) bool { return elem == "" }) if slices.Contains(allOperationSymbols, parts[0]) { p.operations = make([]operation, len(parts)) for idx, sym := range parts { p.operations[idx] = operationFromSymbol(sym) } done = true } else { if len(p.values) == 0 { lenparts := len(parts) p.values = make([][]int, lenparts) for idx := range lenparts { p.values[idx] = []int{} } } for idx, part := range parts { num, _ := strconv.Atoi(part) p.values[idx] = append(p.values[idx], num) } done = false } return done } func (p problems) solve() []int { solutions := make([]int, len(p.values)) for idx, values := range p.values { op := p.operations[idx] solutions[idx] = op.compute(values) } return solutions } func stepOne(input chan string) (int, error) { modernProblems := problems{} for line := range input { done := modernProblems.addLine(line) if done { break } } modernSolutions := modernProblems.solve() sum := 0 for _, solution := range modernSolutions { sum += solution } return sum, nil } func transposeInputChan(input chan string) []string { lines := [][]rune{} for line := range input { lines = append(lines, []rune(line)) } linecount := len(lines) columncount := len(lines[0]) transposed := make([][]rune, columncount) for idx := range transposed { transposed[idx] = make([]rune, linecount) } for row, line := range lines { for col, char := range line { transposed[col][row] = char } } columns := make([]string, len(transposed)) for idx, col := range transposed { columns[idx] = string(col) } return columns } func stepTwo(input chan string) (int, error) { transposedInput := transposeInputChan(input) slices.Reverse(transposedInput) // problem-set with one empty problem. modernProblems := problems{ values: [][]int{[]int{}}, } for _, column := range transposedInput { if matched, _ := regexp.MatchString("^\\s*$", column); matched { continue } modernProblems.feed(column) } // Remove last useless empty problem. modernProblems.values = modernProblems.values[:len(modernProblems.values)-1] modernSolutions := modernProblems.solve() sum := 0 for _, solution := range modernSolutions { sum += solution } return sum, nil } func main() { inputFile := utils.FilePath("day06.txt") utils.RunStep(utils.ONE, inputFile, stepOne) utils.RunStep(utils.TWO, inputFile, stepTwo) }Rust
Mainly difficult parsing today.
fn part1(input: String) { let mut nums: Vec<Vec<u64>> = Vec::new(); let mut mul: Vec<bool> = Vec::new(); for l in input.lines() { if l.chars().next().unwrap().is_ascii_digit() { let row = l .split_ascii_whitespace() .map(|s| s.parse::<u64>().unwrap()) .collect(); nums.push(row); } else { mul = l.split_ascii_whitespace().map(|s| s == "*").collect(); } } let mut sum = 0; for (idx, op_mul) in mul.iter().enumerate() { let col = nums.iter().map(|row| row[idx]); sum += if *op_mul { col.reduce(|acc, n| acc * n) } else { col.reduce(|acc, n| acc + n) } .unwrap(); } println!("{sum}"); } fn part2(input: String) { let grid: Vec<&[u8]> = input.lines().map(|l| l.as_bytes()).collect(); let n_rows = grid.len() - 1; // Not counting operator row let mut op_mul = grid[n_rows][0] == b'*'; let mut cur = if op_mul { 1 } else { 0 }; let mut sum = 0; for x in 0..grid[0].len() { let digits: Vec<u8> = (0..n_rows).map(|y| grid[y][x]).collect(); if digits.iter().all(|d| *d == b' ') { sum += cur; op_mul = grid[n_rows][x + 1] == b'*'; cur = if op_mul { 1 } else { 0 }; continue; } let n = String::from_utf8(digits) .unwrap() .trim() .parse::<u64>() .unwrap(); if op_mul { cur *= n; } else { cur += n; } } sum += cur; println!("{sum}"); } util::aoc_main!();









