import { readFileSync } from 'node:fs'; const input = readFileSync('input', 'utf-8'); const [stackInput, movementInput] = input.split('\n\n', 2); // Parse movements const movements = movementInput .split('\n') .filter(movement => movement) .map(movement => { const match = movement.match(/move (\d+) from (\d+) to (\d+)/); if (!match) { throw new Error(`Invalid movement: ${movement}`); } return { count: parseInt(match[1]), from: parseInt(match[2]), to: parseInt(match[3]) } }); // Parse stacks const stackInputLines = stackInput.split('\n'); const columnDelimiter = ' '; const columnPositions = stackInputLines // Locate delimiter positions .map(line => { const positions = []; let pos = -1; while (true) { pos = line.indexOf(columnDelimiter, pos + 1); if (pos === -1) { break; } positions.push(pos); } return positions; }) .reduce((commonPositions, positions) => commonPositions.filter( pos => positions.includes(pos) ) ); const columns = new Array(columnPositions.length + 1) .fill(0) .map(column => []); for (const line of [...stackInputLines].reverse()) { for (let i = 0; i <= columnPositions.length; i++) { const start = (columnPositions[i - 1] ?? -1) + 1; const end = columnPositions[i] ?? line.length; const value = line.slice(start, end); columns[i].push(value); } } class Stack { constructor(number, crates) { this.number = number; this.crates = crates; } get topCrate() { return this.crates[this.crates.length - 1]; } move(count, toStack) { this._checkMoveCount(count); toStack.crates.push(...this.crates.splice(-count)); } moveIndividually(count, toStack) { this._checkMoveCount(count); toStack.crates.push(...this.crates.splice(-count).reverse()); } _checkMoveCount(count) { if (count > this.crates.length) { throw new Error( `Tried to move ${count} crates from a stack containing: ` + `${this.crates.length}` ); } } } function buildStacks() { return new Map(columns .map(column => column .map(cell => cell.trim()) .filter(cell => cell) ) .map(column => { if (column.length < 1) { throw new Error('Invalid column'); } const number = parseInt(column[0]); if (isNaN(number)) { throw new Error(`Invalid column number: ${number}`); } const crates = column .slice(1) .map(crate => crate.replaceAll(/\[?\]?/g, '')); return new Stack(number, crates); }) .map(stack => [stack.number, stack]) ); } function getTopCrates(stacks) { return [...stacks.keys()] .sort() .map(number => stacks.get(number).topCrate) .reduce((crates, crate) => crates + crate); } function runSimulation(simulationCb) { for (const movement of movements) { const fromStack = stacks.get(movement.from); if (!fromStack) { throw new Error(`Tried to move from unknown stack: ${movement.from}`); } const toStack = stacks.get(movement.to); if (!toStack) { throw new Error(`Tried to move to unknown stack: ${movement.to}`); } simulationCb(movement.count, fromStack, toStack); } } let stacks; let topCrates; // CrateMover 9000 Simulation stacks = buildStacks(); runSimulation((count, fromStack, toStack) => fromStack.moveIndividually(count, toStack) ); topCrates = getTopCrates(stacks); console.log(`Top crates after CrateMover 9000 sort: ${topCrates}`); // CrateMover 9001 Simulation stacks = buildStacks(); runSimulation((count, fromStack, toStack) => fromStack.move(count, toStack) ); topCrates = getTopCrates(stacks); console.log(`Top crates after CrateMover 9001 sort: ${topCrates}`);