generated from eric/adventofcode2023
Day twelve both parts
This commit is contained in:
197
12/code.js
Normal file
197
12/code.js
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
|
||||||
|
let sampleMode = false;
|
||||||
|
let usedArray = [];
|
||||||
|
|
||||||
|
const sampleArray = readFileSync('sample.txt').toString().split("\n");
|
||||||
|
const inputArray = readFileSync('input.txt').toString().split("\n");
|
||||||
|
|
||||||
|
if (sampleMode) {
|
||||||
|
usedArray = sampleArray;
|
||||||
|
} else {
|
||||||
|
usedArray = inputArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part One
|
||||||
|
|
||||||
|
console.time("part1");
|
||||||
|
|
||||||
|
// Parse input
|
||||||
|
const shapes = [];
|
||||||
|
const regions = [];
|
||||||
|
|
||||||
|
let idx = 0;
|
||||||
|
// Parse shapes - continue until we hit a region line (NxN: ...)
|
||||||
|
while (idx < usedArray.length) {
|
||||||
|
const regionMatch = usedArray[idx].match(/^(\d+)x(\d+): (.+)$/);
|
||||||
|
if (regionMatch) break; // Start of regions section
|
||||||
|
|
||||||
|
const shapeMatch = usedArray[idx].match(/^(\d+):$/);
|
||||||
|
if (shapeMatch) {
|
||||||
|
const shapeLines = [];
|
||||||
|
idx++;
|
||||||
|
while (idx < usedArray.length && usedArray[idx] !== '' && !usedArray[idx].match(/^\d+:/) && !usedArray[idx].match(/^\d+x\d+:/)) {
|
||||||
|
shapeLines.push(usedArray[idx]);
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
// Convert shape to list of coordinates
|
||||||
|
const coords = [];
|
||||||
|
for (let y = 0; y < shapeLines.length; y++) {
|
||||||
|
for (let x = 0; x < shapeLines[y].length; x++) {
|
||||||
|
if (shapeLines[y][x] === '#') {
|
||||||
|
coords.push([x, y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shapes.push(coords);
|
||||||
|
} else {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse regions
|
||||||
|
while (idx < usedArray.length) {
|
||||||
|
if (usedArray[idx] === '') { idx++; continue; }
|
||||||
|
const match = usedArray[idx].match(/^(\d+)x(\d+): (.+)$/);
|
||||||
|
if (match) {
|
||||||
|
const width = Number(match[1]);
|
||||||
|
const height = Number(match[2]);
|
||||||
|
const counts = match[3].split(' ').map(Number);
|
||||||
|
regions.push({ width, height, counts });
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate all rotations and flips of a shape
|
||||||
|
function getAllOrientations(coords) {
|
||||||
|
const orientations = new Set();
|
||||||
|
let current = coords;
|
||||||
|
|
||||||
|
for (let flip = 0; flip < 2; flip++) {
|
||||||
|
for (let rot = 0; rot < 4; rot++) {
|
||||||
|
// Normalize: translate to origin (min x = 0, min y = 0)
|
||||||
|
const minX = Math.min(...current.map(c => c[0]));
|
||||||
|
const minY = Math.min(...current.map(c => c[1]));
|
||||||
|
const normalized = current.map(c => [c[0] - minX, c[1] - minY]);
|
||||||
|
normalized.sort((a, b) => a[1] - b[1] || a[0] - b[0]);
|
||||||
|
orientations.add(JSON.stringify(normalized));
|
||||||
|
|
||||||
|
// Rotate 90 degrees: (x, y) -> (-y, x)
|
||||||
|
current = current.map(c => [-c[1], c[0]]);
|
||||||
|
}
|
||||||
|
// Flip horizontally: (x, y) -> (-x, y)
|
||||||
|
current = coords.map(c => [-c[0], c[1]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...orientations].map(s => JSON.parse(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precompute all orientations for all shapes
|
||||||
|
const shapeOrientations = shapes.map(getAllOrientations);
|
||||||
|
|
||||||
|
// Check if a region can fit all required presents
|
||||||
|
function canFit(width, height, counts) {
|
||||||
|
const grid = Array(height).fill(null).map(() => Array(width).fill(false));
|
||||||
|
|
||||||
|
// Build list of pieces to place
|
||||||
|
const pieces = [];
|
||||||
|
for (let shapeIdx = 0; shapeIdx < counts.length; shapeIdx++) {
|
||||||
|
for (let j = 0; j < counts[shapeIdx]; j++) {
|
||||||
|
pieces.push(shapeIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total cells needed - must not exceed region size
|
||||||
|
const totalCells = pieces.reduce((sum, shapeIdx) => sum + shapes[shapeIdx].length, 0);
|
||||||
|
if (totalCells > width * height) return false; // Can't exceed region size
|
||||||
|
|
||||||
|
if (pieces.length === 0) return true;
|
||||||
|
|
||||||
|
function canPlace(orientedShape, startX, startY) {
|
||||||
|
for (const [dx, dy] of orientedShape) {
|
||||||
|
const x = startX + dx;
|
||||||
|
const y = startY + dy;
|
||||||
|
if (x < 0 || x >= width || y < 0 || y >= height || grid[y][x]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function place(orientedShape, startX, startY) {
|
||||||
|
for (const [dx, dy] of orientedShape) {
|
||||||
|
grid[startY + dy][startX + dx] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unplace(orientedShape, startX, startY) {
|
||||||
|
for (const [dx, dy] of orientedShape) {
|
||||||
|
grid[startY + dy][startX + dx] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track placement positions for each shape type to avoid duplicates
|
||||||
|
const lastPlacement = new Map(); // shapeIdx -> [x, y] of last placement
|
||||||
|
|
||||||
|
function solve(pieceIdx) {
|
||||||
|
if (pieceIdx >= pieces.length) {
|
||||||
|
return true; // All pieces placed successfully
|
||||||
|
}
|
||||||
|
|
||||||
|
const shapeIdx = pieces[pieceIdx];
|
||||||
|
const orientations = shapeOrientations[shapeIdx];
|
||||||
|
|
||||||
|
// Get minimum position for this shape type (to avoid duplicate placements of same shape)
|
||||||
|
const minPos = lastPlacement.get(shapeIdx);
|
||||||
|
|
||||||
|
for (const oriented of orientations) {
|
||||||
|
// Try all valid placements in the grid
|
||||||
|
for (let startY = 0; startY < height; startY++) {
|
||||||
|
for (let startX = 0; startX < width; startX++) {
|
||||||
|
// Skip if we've already placed this shape type at a later position
|
||||||
|
// (enforces canonical ordering for identical pieces)
|
||||||
|
if (minPos && (startY < minPos[1] || (startY === minPos[1] && startX <= minPos[0]))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canPlace(oriented, startX, startY)) {
|
||||||
|
place(oriented, startX, startY);
|
||||||
|
const prevPos = lastPlacement.get(shapeIdx);
|
||||||
|
lastPlacement.set(shapeIdx, [startX, startY]);
|
||||||
|
if (solve(pieceIdx + 1)) return true;
|
||||||
|
if (prevPos) {
|
||||||
|
lastPlacement.set(shapeIdx, prevPos);
|
||||||
|
} else {
|
||||||
|
lastPlacement.delete(shapeIdx);
|
||||||
|
}
|
||||||
|
unplace(oriented, startX, startY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return solve(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fitCount = 0;
|
||||||
|
for (const region of regions) {
|
||||||
|
if (canFit(region.width, region.height, region.counts)) {
|
||||||
|
fitCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.timeEnd("part1");
|
||||||
|
console.log(fitCount);
|
||||||
|
|
||||||
|
|
||||||
|
// Part Two
|
||||||
|
|
||||||
|
console.time("part2");
|
||||||
|
|
||||||
|
console.timeEnd("part2");
|
||||||
|
console.log("Free star!");
|
||||||
|
|
||||||
|
// functions
|
||||||
Reference in New Issue
Block a user