155 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			155 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
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}`);
 |