diff --git a/t.js b/t.js deleted file mode 100644 index 80074f9..0000000 --- a/t.js +++ /dev/null @@ -1,1494 +0,0 @@ -const opens = ["(", "[", "{", "<"] -const closes = [")", "]", "}", ">"] -const scores1 = { ")": 3, "]": 57, "}": 1197, ">": 25137 } -const scores2 = { ")": 1, "]": 2, "}": 3, ">": 4 } - -function day10(input, part2) { - let results = input.split("\n").map((str) => { - let stack = [] - - for (let i = 0; i < str.length; i++) { - let char = str[i] - - if (opens.includes(char)) { - stack.push(closes[opens.indexOf(char)]) - } else if (char != stack.pop()) { - return { error: true, score: scores1[char] } - } - } - - return { error: false, score: stack.reduceRight((a, b) => a * 5 + scores2[b], 0) } - }) - - return results.filter((e) => part2 ^ e.error).map((e) => e.score)[part2 ? "medianNumeric" : "sum"]() -} - -if (typeof window == "undefined") { - module.exports = day10 -} -function day11(input, part2) { - let grid = Grid.fromStr(input).mapMut((e) => ({ energy: +e, flashed: false })) - - let steps = 0 - let flashes = 0 - let totalFlashes = 0 - - while (part2 ? flashes != grid.width * grid.height : steps < 100) { - flashes = 0 - - grid.forEach(function flash(e, pt, g) { - e.energy++ - - if (!e.flashed && e.energy > 9) { - e.flashed = true - flashes++ - - g.getAllNeighborsThat(pt, (e) => !e.flashed).forEach((pt) => flash(g.get(pt), pt, g)) - } - }) - - grid.forEach((e) => { - if (e.flashed) { - e.energy = 0 - e.flashed = false - } - }) - - steps++ - totalFlashes += flashes - } - - return part2 ? steps : totalFlashes -} - -if (typeof window == "undefined") { - module.exports = day11 -} -// this one used to be a lot shorter but i made it less shoddy and slow at expense of line count -function day12(input, part2) { - let cxns = input.split("\n").map((line) => line.split("-")) - - let caves = [] - let startId - - for (let cxn of cxns) { - let ids = [] - - for (let i = 0; i < 2; i++) { - let cave = caves.find((e) => e.name == cxn[i]) - - if (!cave) { - caves.push(cave = { - name: cxn[i], - id: caves.length, - isStart: cxn[i] == "start", - isEnd: cxn[i] == "end", - isSmall: cxn[i] == cxn[i].toLowerCase(), - cxns: new Set() - }) - - if (cave.isStart) { - startId = cave.id - } - } - - ids.push(cave.id) - } - - caves[ids[0]].cxns.add(ids[1]) - caves[ids[1]].cxns.add(ids[0]) - } - - let count = 0 - let paths = [{ - cur: startId, - smallVisited: new Set(), - visitedSmallTwice: false - }] - - let i = 0 - - while (paths.length) { - let newPaths = [] - - for (let path of paths) { - for (let nextId of caves[path.cur].cxns) { - let next = caves[nextId] - - if (next.isStart) { - continue - } - - if (next.isEnd) { - count++ - continue - } - - let visitedSmallTwice = path.visitedSmallTwice - - if (next.isSmall && path.smallVisited.has(nextId)) { - if (part2 && !visitedSmallTwice) { - visitedSmallTwice = true - } else { - continue - } - } - - newPaths.push({ - cur: nextId, - smallVisited: new Set(path.smallVisited).add(nextId), - visitedSmallTwice: visitedSmallTwice - }) - } - } - - paths = newPaths - } - - return count -} - -if (typeof window == "undefined") { - module.exports = day12 -} -function day13(input, part2) { - let lines = input.split("\n").splitOnElement("") - - let points = lines[0].map((e) => new Point(...e.split(",").num())).pt - let instructions = lines[1].map((e) => e.split(" ").last.split("=")).map((e) => [e[0], +e[1]]) - - for (let instruction of instructions) { - let pos = instruction[1] - let newPoints = [].pt - - for (let point of points) { - if (instruction[0] == "x") { - newPoints.push(point.x < pos ? point : new Point(2 * pos - point.x, point.y)) - } else { - newPoints.push(point.y < pos ? point : new Point(point.x, 2 * pos - point.y)) - } - } - - points = newPoints.uniq() - - if (!part2) { - return points.length - } - } - - return new Grid(points.max((e) => e.x).x + 1, points.max((e) => e.y).y + 1).mapMut((_, pt) => pt.isIn(points) ? "#" : " ").toString("") -} - -if (typeof window == "undefined") { - module.exports = day13 -} -function getNum(letter) { - return letter.charCodeAt(0) - "A".charCodeAt(0) -} - -function day14(input, part2) { - let lines = input.split("\n").splitOnElement("") - - let init = lines[0][0].split("") - let rules = lines[1].map((e) => e.split(" -> ")) - - let counts = {} - - for (let i = 0; i < init.length - 1; i++) { - let substr = init[i] + init[i + 1] - counts[substr] = (counts[substr] || 0) + 1 - } - - for (let i = 0; i < (part2 ? 40 : 10); i++) { - let changes = {} - - for (let rule of rules) { - let target = rule[0][0] + rule[0][1] - let left = rule[0][0] + rule[1] - let right = rule[1] + rule[0][1] - - changes[left] = (changes[left] || 0) + (counts[target] || 0) - changes[right] = (changes[right] || 0) + (counts[target] || 0) - changes[target] = (changes[target] || 0) - (counts[target] || 0) - } - - for (let pair in changes) { - counts[pair] = (counts[pair] || 0) + changes[pair] - } - } - - // this double counts all letters except the first and last so we add one to those and / 2 at end - let letters = Array(26).fill(0) - letters[getNum(init[0])]++ - letters[getNum(init[init.length - 1])]++ - - for (let pair in counts) { - letters[getNum(pair[0])] += counts[pair] - letters[getNum(pair[1])] += counts[pair] - } - - letters = letters.truthy() - - return (letters.max() - letters.min()) / 2 -} - -if (typeof window == "undefined") { - module.exports = day14 -} -function encodePoint(pt) { - return (pt.x << 9) | pt.y -} - -function decodePoint(num) { - return new Point((num >> 9) & 0b111111111, num & 0b111111111) -} - -function day15(input, part2) { - let grid = Grid.fromStr(input).num() - - if (part2) { - grid = new Grid(grid.width * 5, grid.height * 5) - .mapMut((e, pt) => grid.get(new Pt(pt.x % grid.width, pt.y % grid.height)) + (pt.x / grid.width | 0) + (pt.y / grid.height | 0)) - .mapMut((e) => ((e - 1) % 9) + 1) - } - - let start = encodePoint(new Point(0, 0)) - - let visited = {} - visited[start] = true - - let heap = new BinHeap((a, b) => a.risk < b.risk) - heap.insert({ pt: start, risk: 0 }) - - while (true) { - let top = heap.extract() - let decoded = decodePoint(top.pt) - - if (decoded.x == grid.width - 1 && decoded.y == grid.height - 1) { - return top.risk - } - - for (let pt of grid.getAdjNeighbors(decoded)) { - let encoded = encodePoint(pt) - let risk = top.risk + grid.get(pt) - - if (encoded in visited) { - let idx = heap.data.findIndex((e) => e.pt == encoded) - if (idx > -1 && risk < heap.data[idx].risk) { - heap.data[idx].risk = risk - heap.up(idx) - } - } else { - visited[encoded] = true - heap.insert({ pt: encoded, risk: risk }) - } - } - } -} - -if (typeof window == "undefined") { - module.exports = day15 -} -function toInt(bits) { - return parseInt(bits.join(""), 2) -} - -class Packet { - full = [] - - version = [] - type = [] - groups = [[]] - lengthType = [] - length = [] - - children = [] - done = false - - constructor(bits = []) { - for (let bit of bits) { - this.pushBit(bit) - } - } - - pushBit(bit) { - if (this.version.length < 3) { - this.version.push(bit) - return this.full.push(bit) - } - - if (this.type.length < 3) { - this.type.push(bit) - return this.full.push(bit) - } - - if (this.isLiteral()) { - if (this.groups.last.length < 5) { - this.groups.last.push(bit) - return this.full.push(bit) - } - - if (this.groups.last[0]) { - this.groups.push([bit]) - return this.full.push(bit) - } - } else { - if (this.children.length == 0) { - this.children.push(new Packet()) - } - - if (this.lengthType.length < 1) { - this.lengthType.push(bit) - return this.full.push(bit) - } - - if (!this.lengthType[0]) { - if (this.length.length < 15) { - this.length.push(bit) - return this.full.push(bit) - } - - if (this.children.map((e) => e.full.length).sum() < toInt(this.length)) { - this.children.last.pushBit(bit) - - if (this.children.last.done) { - this.children.push(new Packet([bit])) - } - - return this.full.push(bit) - } - } else { - if (this.length.length < 11) { - this.length.push(bit) - return this.full.push(bit) - } - - if (!this.children.last.done) { - this.children.last.pushBit(bit) - - if (this.children.last.done) { - if (this.children.length < toInt(this.length)) { - this.children.push(new Packet([bit])) - return this.full.push(bit) - } - } else { - return this.full.push(bit) - } - } - } - } - - this.done = true - } - - isLiteral() { - return toInt(this.type) == 4 - } - - getLiteralValue() { - return toInt(this.groups.flatMap((e) => e.slice(1))) - } - - getVersionSum() { - return toInt(this.version) + this.children.map((e) => e.getVersionSum()).sum() - } - - getValue() { - if (this.isLiteral()) { - return this.getLiteralValue() - } else { - let operands = this.children.map((e) => e.getValue()) - - switch (toInt(this.type)) { - case 0: - return operands.sum() - - case 1: - return operands.prod() - - case 2: - return operands.min() - - case 3: - return operands.max() - - case 5: - return +(operands[0] > operands[1]) - - case 6: - return +(operands[0] < operands[1]) - - case 7: - return +(operands[0] == operands[1]) - } - } - } -} - -function day16(input, part2) { - let bits = input.split("").flatMap((e) => parseInt(e, 16).toString(2).padStart(4, "0").split("").num()) - return new Packet(bits)[part2 ? "getValue" : "getVersionSum"]() -} - -if (typeof window == "undefined") { - module.exports = day16 -} -function day17(input, part2) { - let [minX, maxX, minY, maxY] = input.match(/([-\d]+)/g).num() - - let xVelRange = utils.signAgnosticInclusiveRange(0, maxX) - let yVelRange = utils.signAgnosticInclusiveRange(-minY, minY) - - let vels = xVelRange.cartProduct(yVelRange).map((e) => new Point(...e)).pt.filter((e) => { - let pos = new Point(0, 0) - let vel = e.copy() - - while (pos.x <= maxX && pos.y >= minY) { - if (pos.x >= minX && pos.y <= maxY) { - return true - } - - pos.addMut(vel) - - if (vel.x > 0) { - vel.x-- - } - - vel.y-- - - if (vel.y == 0) { - e.maxYPos = pos.y - } - } - - return false - }) - - if (!part2) { - // i feel like this is probably equivalent to minY * (minY + 1) / 2 but im not 100% sure for all inputs - return vels.max((e) => e.y).maxYPos - } else { - return vels.length - } -} - -if (typeof window == "undefined") { - module.exports = day17 -} -class SnailfishNumber { - static fromArr(arr, parent = null, isRight) { - let num = new SnailfishNumber() - return num.initialize( - arr[0] instanceof Array ? SnailfishNumber.fromArr(arr[0], num, false) : arr[0], - arr[1] instanceof Array ? SnailfishNumber.fromArr(arr[1], num, true) : arr[1], - parent, isRight) - } - - initialize(left, right, parent, isRight) { - this.left = left - this.right = right - this.parent = parent - this.isRight = isRight - return this - } - - explode(depth = 0) { - if (this.left instanceof SnailfishNumber) { - if (this.left.explode(depth + 1)) { - return true - } - } - - if (this.right instanceof SnailfishNumber) { - if (this.right.explode(depth + 1)) { - return true - } - } - - if (!(this.left instanceof SnailfishNumber) && !(this.right instanceof SnailfishNumber) && depth >= 4) { - let lastNode = this - let node = this.parent - - while (node && node.left == lastNode) { - lastNode = node - node = node.parent - } - - if (node) { - if (!(node.left instanceof SnailfishNumber)) { - node.left += this.left - } else { - node = node.left - - while (node.right instanceof SnailfishNumber) { - node = node.right - } - - node.right += this.left - } - } - - lastNode = this - node = this.parent - - while (node && node.right == lastNode) { - lastNode = node - node = node.parent - } - - if (node) { - if (!(node.right instanceof SnailfishNumber)) { - node.right += this.right - } else { - node = node.right - - while (node.left instanceof SnailfishNumber) { - node = node.left - } - - node.left += this.right - } - } - - if (!this.isRight) { - this.parent.left = 0 - } else { - this.parent.right = 0 - } - - return true - } - - return false - } - - split() { - if (this.left instanceof SnailfishNumber) { - if (this.left.split()) { - return true - } - } else if (this.left >= 10) { - this.left = SnailfishNumber.fromArr([Math.floor(this.left / 2), Math.ceil(this.left / 2)], this, false) - return true - } - - if (this.right instanceof SnailfishNumber) { - if (this.right.split()) { - return true - } - } else if (this.right >= 10) { - this.right = SnailfishNumber.fromArr([Math.floor(this.right / 2), Math.ceil(this.right / 2)], this, true) - return true - } - - return false - } - - reduce() { - while (this.explode() || this.split()); - return this - } - - add(that) { - let parent = SnailfishNumber.fromArr([this, that]) - this.parent = parent - this.isRight = false - that.parent = parent - that.isRight = true - return parent.reduce() - } - - magnitude() { - return 3 * (this.left instanceof SnailfishNumber ? this.left.magnitude() : this.left) + - 2 * (this.right instanceof SnailfishNumber ? this.right.magnitude() : this.right) - } - - toString() { - return "[" + this.left.toString() + "," + this.right.toString() + "]" - } -} - -function day18(input, part2) { - let lines = input.split("\n") - - if (!part2) { - let nums = lines.map((line) => SnailfishNumber.fromArr(JSON.parse(line))) - return nums.reduce((a, b) => a.add(b)).magnitude() - } else { - let pairs = lines.flatMap((e) => lines.filter((f) => e != f).map((f) => [e, f].map((e) => SnailfishNumber.fromArr(JSON.parse(e))))) - return pairs.map((e) => e[0].add(e[1]).magnitude()).max() - } -} - -if (typeof window == "undefined") { - module.exports = day18 -} -function allRotations(pt) { - return new PointArray( - new Point(pt.x, pt.y, pt.z), // facing +x, +z up - new Point(pt.x, pt.z, -pt.y), - new Point(pt.x, -pt.y, -pt.z), - new Point(pt.x, -pt.z, pt.y), - new Point(-pt.x, -pt.y, pt.z), // facing -x, +z up - new Point(-pt.x, -pt.z, -pt.y), - new Point(-pt.x, pt.y, -pt.z), - new Point(-pt.x, pt.z, pt.y), - new Point(-pt.y, pt.x, pt.z), // facing +y, +z up - new Point(-pt.y, pt.z, -pt.x), - new Point(-pt.y, -pt.x, -pt.z), - new Point(-pt.y, -pt.z, pt.x), - new Point(pt.y, -pt.x, pt.z), // facing -y, +z up - new Point(pt.y, -pt.z, -pt.x), - new Point(pt.y, pt.x, -pt.z), - new Point(pt.y, pt.z, pt.x), - new Point(pt.z, -pt.y, pt.x), // facing +z, -x up - new Point(pt.z, -pt.x, -pt.y), - new Point(pt.z, pt.y, -pt.x), - new Point(pt.z, pt.x, pt.y), - new Point(-pt.z, pt.y, pt.x), // facing -z, +x up - new Point(-pt.z, pt.x, -pt.y), - new Point(-pt.z, -pt.y, -pt.x), - new Point(-pt.z, -pt.x, pt.y) - ) -} - -const rotationsProto = allRotations(new Point(1, 2, 3)) - -const composedRotations = rotationsProto.mapArr((e) => allRotations(e).mapArr((e) => rotationsProto.indexOf(e))) -const inverseRotations = composedRotations.map((e) => e.indexOf(0)) - -function rotate(pt, t) { - return allRotations(pt)[t < 0 ? inverseRotations[-t] : t] -} - -class Transform { - constructor(translate, rotate) { - this.translate = translate - this.rotate = rotate - } - - transform(pt) { - return rotate(pt.add(this.translate), this.rotate) - } - - // R(x + T) = x' - // x = R'(x') - T = R'(x' - R(T)) - invert() { - return new Transform(rotate(this.translate, this.rotate).neg(), inverseRotations[this.rotate]) - } - - // x' = R2(R1(x + T1) + T2) = R2(R1(x) + R1(T1) + T2) - // T' = R1(T1) + T2; x' = (R2 o R1)((x) + R1^-1 (T')) - compose(that) { - return new Transform( - rotate(rotate(this.translate, this.rotate).add(that.translate), inverseRotations[this.rotate]), - composedRotations[this.rotate][that.rotate] - ) - } -} - -function day19(input, part2) { - let lines = input.split("\n") - - let scans = new PointArray() - - for (let line of lines) { - if (line.includes("---")) { - scans.push(new PointArray()) - } else if (line) { - scans.last.push(new Point(...line.split(",").num())) - } - } - - let transforms = Array(scans.length).fill().map((_, i) => { - let a = Array(scans.length).fill() - a[i] = new Transform(new Point(0, 0, 0), 0) - return a - }) - - let recurse = function(i) { - let scan = scans[i] - let sharedCount = 0 - - scan.checked = true - - for (let j = 0; j < scan.length - 11 - sharedCount; j++) { - let scanRel = scan.map((e) => e.sub(scan[j])) - let shared = false - - for (let k = 0; k < scans.length; k++) { - if (scans[k].checked) { - continue - } - - out: for (let t = 0; t < 24; t++) { - let scan2 = scans[k].map((e) => rotate(e, t)) - - for (let l = 0; l < scan2.length; l++) { - let scanRel2 = scan2.map((e) => e.sub(scan2[l])) - - let count = 0 - - for (let m = 0; m < scanRel.length; m++) { - if (m > scanRel.length - 12 + count) { - break - } - - for (let pt of scanRel2) { - if (scanRel[m].equals(pt)) { - count++ - } - } - } - - if (count >= 12) { - transforms[i][k] = new Transform(scan2[l].sub(scan[j]), inverseRotations[t]) - transforms[k][i] = transforms[i][k].invert() - - for (let m = 0; m < transforms.length; m++) { - if (m != i && transforms[m][i] && !transforms[m][k]) { - transforms[m][k] = transforms[m][i].compose(transforms[i][k]) - transforms[k][m] = transforms[m][k].invert() - } - - if (m != k && transforms[m][k] && !transforms[m][i]) { - transforms[m][i] = transforms[m][k].compose(transforms[k][i]) - transforms[i][m] = transforms[m][i].invert() - } - } - - console.log(`linked ${i}<->${k}\t(${i} point ${j} == ${k} point ${l} with rotation ${t})`) - - recurse(k) - - shared = true - break out - } - } - } - } - - if (shared) { - sharedCount++ - } - } - } - - recurse(0) - - if (!part2) { - let beacons = new PointArray() - - for (let i = 0; i < scans.length; i++) { - beacons.pushUniq(...scans[i].map((pt) => transforms[i][0].transform(pt))) - } - - return beacons.length - } else { - return transforms.flatMap((e) => e.map((f) => Math.abs(f.translate.x) + Math.abs(f.translate.y) + Math.abs(f.translate.z))).max() - } -} - -if (typeof window == "undefined") { - module.exports = day19 -} -function day1(input, part2) { - return input.split("\n").num().count((e, i, a, k = a[i - (part2 ? 3 : 1)]) => k && e > k) -} - -if (typeof window == "undefined") { - module.exports = day1 -} -function isEdge(pt, g) { - return pt.x == 0 || pt.x == g.width - 1 || pt.y == 0 || pt.y == g.height - 1 -} - -function expand(grid, fill) { - return new Grid(grid.width + 2, grid.height + 2).mapMut((_, pt, g) => isEdge(pt, g) ? fill : grid.get(pt.ul())) -} - -function day20(input, part2) { - let lines = input.split("\n").splitOnElement("") - - let code = lines[0][0].split("").map((e) => e == "#") - - let width = lines[1][0].length - let height = lines[1].length - - let grid = expand(expand(Grid.fromArr(lines[1]).mapMut((e) => e == "#"), false), false) - - for (let i = 0; i < (part2 ? 50 : 2); i++) { - grid = new Grid(grid.width, grid.height).mapMut((_, pt, g) => code[pt.getUnfilteredAllNeighborsIncSelf().map((pt2) => grid.get(grid.contains(pt2) ? pt2 : pt)).reduce((a, b) => 2 * a + b)]) - grid = expand(grid, grid.get(new Point(0, 0))) - } - - return grid.findAll((e) => e).length -} - -if (typeof window == "undefined") { - module.exports = day20 -} -// turn: 0-1; 1 bit (0 = player 1; 1 = player 2) -// pos1: 1-10; 4 bits -// pos2: 1-10; 4 bits -// score1: 0-1009; 10 bits -// score2: 0-1009; 10 bits -const sizeS = 10 -const sizeP = 4 -const sizeT = 1 - -const maskS = (1 << sizeS) - 1 -const maskP = (1 << sizeP) - 1 -const maskT = (1 << sizeT) - 1 - -const offsetS2 = 0 -const offsetS1 = offsetS2 + sizeS -const offsetP2 = offsetS1 + sizeS -const offsetP1 = offsetP2 + sizeP -const offsetT = offsetP1 + sizeP - -function encodeState(turn, pos1, pos2, score1, score2) { - return (score2 << offsetS2) | - (score1 << offsetS1) | - (pos2 << offsetP2) | - (pos1 << offsetP1) | - (turn << offsetT) -} - -function getScore(state, player) { - return state >> (player ? offsetS2 : offsetS1) & maskS -} - -function advState(state, roll, win) { - let turn = state >> offsetT - - let offsetPos = turn ? offsetP2 : offsetP1 - let offsetScore = turn ? offsetS2 : offsetS1 - - let pos = (state >> offsetPos) & maskP - let score = (state >> offsetScore) & maskS - - pos = (((pos + roll) - 1) % 10) + 1 - score += pos - - if (score >= win) { - return turn - } - - state &= ~(maskP << offsetPos) - state |= pos << offsetPos - - state &= ~(maskS << offsetScore) - state |= score << offsetScore - - state ^= 1 << offsetT - - return state -} - -const rollCombos = [1, 2, 3].cartProduct([1, 2, 3]).cartProduct([1, 2, 3]).map((e) => e.flat().sum()) - -function day21(input, part2) { - let lines = input.split("\n") - - let pos1 = +lines[0].split(" ").last - let pos2 = +lines[1].split(" ").last - - let state = encodeState(0, pos1, pos2, 0, 0) - - if (!part2) { - const win = 1000 - - let die = 0 - let rolls = 0 - - while (getScore(state, 0) < win && getScore(state, 1) < win) { - let roll = ++die - die %= 100 - roll += ++die - die %= 100 - roll += ++die - die %= 100 - - rolls += 3 - - newState = advState(state, roll, win) - - if (newState == 0 || newState == 1) { - return getScore(state, 1 - newState) * rolls - } - - state = newState - } - } else { - let universes = {} - universes[state] = 1 - - let wins = [0, 0] - - while (true) { - let universesLeft = false - - for (let state in universes) { - if (!universes[state]) { - continue - } - - universesLeft = true - - state = +state - - for (let roll of rollCombos) { - let newState = advState(state, roll, 21) - - if (newState == 0 || newState == 1) { - wins[newState] += universes[state] - } else { - universes[newState] = (universes[newState] || 0) + universes[state] - } - } - - delete universes[state] - } - - if (!universesLeft) { - break - } - } - - return wins.max() - } -} - -if (typeof window == "undefined") { - module.exports = day21 -} -class Cuboid { - constructor(sgn, xs, xe, ys, ye, zs, ze) { - this.sgn = sgn - this.xs = Math.min(xs, xe) - this.xe = Math.max(xs, xe) - this.ys = Math.min(ys, ye) - this.ye = Math.max(ys, ye) - this.zs = Math.min(zs, ze) - this.ze = Math.max(zs, ze) - } - - vol() { return (this.xe - this.xs + 1) * (this.ye - this.ys + 1) * (this.ze - this.zs + 1) } - - int(that) { - if (that.xe < this.xs || this.xe < that.xs) return - if (that.ye < this.ys || this.ye < that.ys) return - if (that.ze < this.zs || this.ze < that.zs) return - - let sgn = -that.sgn - - let xs = Math.max(this.xs, that.xs) - let xe = Math.min(this.xe, that.xe) - let ys = Math.max(this.ys, that.ys) - let ye = Math.min(this.ye, that.ye) - let zs = Math.max(this.zs, that.zs) - let ze = Math.min(this.ze, that.ze) - - return new Cuboid(sgn, xs, xe, ys, ye, zs, ze) - } -} - -function day20(input, part2) { - let lines = input.split("\n") - - let cuboids = [] - - for (let line of lines) { - let data = line.match(/^(.+) x=(.+)\.\.(.+),y=(.+)\.\.(.+),z=(.+)\.\.(.+)$/) - let cuboid = new Cuboid(data[1] == "on", +data[2], +data[3], +data[4], +data[5], +data[6], +data[7]) - - if (!part2 && ( - cuboid.xs < -50 || cuboid.xe > 50 || - cuboid.ys < -50 || cuboid.ye > 50 || - cuboid.zs < -50 || cuboid.ze > 50)) { - continue - } - - let toAdd = [cuboid] - - for (let otherCuboid of cuboids) { - let intersection = cuboid.int(otherCuboid) - - if (intersection) { - toAdd.push(intersection) - } - } - - cuboids.push(...toAdd) - } - - return cuboids.map((e) => e.vol() * e.sgn).sum() -} - -if (typeof window == "undefined") { - module.exports = day20 -} -const statesArr = [] -const stateIdxs = {} - -function encodeState(arr) { - let key = arr.join("") - - if (!(key in stateIdxs)) { - stateIdxs[key] = statesArr.push(arr) - 1 - } - - return stateIdxs[key] -} - -function decodeState(num) { - return statesArr[num] -} - -// 0 is hallway, 1 is A room, 2 is B room, etc -function getRoom(pos) { - if (pos < 11) { - return 0 - } - - return ((pos - 11) % 4) + 1 -} - -let hallwayStops = [0, 1, 3, 5, 7, 9, 10] -let roomPoses = [] - -function possibleStates(num) { - let state = decodeState(num) - - let newStates = [] - - for (let pos = 0; pos < state.length; pos++) { - let amphipod = state[pos] - - if (!amphipod) { - continue - } - - let curRoom = getRoom(pos) - - let roomEntrance = 2 * curRoom - let destRoomEntrance = 2 * amphipod - - let curRoomPoses = roomPoses[getRoom(pos)] - let destRoomPoses = roomPoses[amphipod] - - let depthInRoom = curRoomPoses?.indexOf(pos) - - if (curRoom == amphipod && curRoomPoses.slice(depthInRoom).every((e) => state[e] == amphipod)) { - continue - } - - let dests = [] - - let destRoomAvailable = destRoomPoses.every((pos) => state[pos] == 0 || state[pos] == amphipod) - - let steps = 0 - let hallwayPos - - if (curRoom != 0) { - if (curRoomPoses.slice(0, depthInRoom).some((pos) => state[pos] != 0)) { - continue - } - - destRoomAvailable &&= roomEntrance != destRoomEntrance - - steps = depthInRoom + 1 - hallwayPos = roomEntrance - } else { - hallwayPos = pos - } - - let destRoomSteps = -1 - - let leftPath = hallwayStops.filter((pos) => pos < hallwayPos).reverse() - let rightPath = hallwayStops.filter((pos) => pos > hallwayPos) - - let leftBlock = leftPath.findIndex((pos) => state[pos] != 0) - let rightBlock = rightPath.findIndex((pos) => state[pos] != 0) - - let leftDests = (leftBlock > -1 ? leftPath.slice(0, leftBlock) : leftPath) - .map((pos) => [pos, steps + hallwayPos - pos]) - let rightDests = (rightBlock > -1 ? rightPath.slice(0, rightBlock) : rightPath) - .map((pos) => [pos, steps + pos - hallwayPos]) - - if (curRoom != 0) { - dests.push(...leftDests, ...rightDests) - } - - if (destRoomAvailable) { - destRoomSteps = - [...leftDests, [hallwayPos, steps]].find((e) => e[0] == destRoomEntrance + 1)?.[1] ?? - [...rightDests, [hallwayPos, steps]].find((e) => e[0] == destRoomEntrance - 1)?.[1] ?? -1 - } - - if (destRoomSteps > -1) { - let destRoomBlock = destRoomPoses.findIndex((pos) => state[pos] != 0) - let destPosIdx = (destRoomBlock > -1 ? destRoomBlock : destRoomPoses.length) - 1 - dests.push([destRoomPoses[destPosIdx], destRoomSteps + 2 + destPosIdx]) - } - - newStates.push(...dests.map((e) => { - let newState = state.copy() - - newState[pos] = 0 - newState[e[0]] = amphipod - - return { state: encodeState(newState), energy: e[1] * [1, 10, 100, 1000][amphipod - 1] } - })) - } - - return newStates -} - -function isSolved(num) { - let state = decodeState(num) - return roomPoses.every((poses, i) => poses.every((pos) => state[pos] == i)) -} - -function day23(input, part2) { - let lines = input.split("\n") - - if (part2) { - lines.splice(3, 0, " #D#C#B#A#", " #D#B#A#C#") - } - - for (let i = 1; i <= 4; i++) { - roomPoses[i] = Array(part2 ? 4 : 2).fill().map((_, j) => 10 + j * 4 + i) - } - - let state = encodeState(lines.join("").replace(/[# ]/g, "") - .replaceAll(".", 0) - .replaceAll("A", 1) - .replaceAll("B", 2) - .replaceAll("C", 3) - .replaceAll("D", 4).split("").num()) - - let energies = {} - energies[state] = 0 - - let stateHeap = new BinHeap((state1, state2) => state1.totalEnergy < state2.totalEnergy) - stateHeap.insert({ state: state, totalEnergy: 0 }) - - for (let i = 0; ; i++) { - let top = stateHeap.extract() - - if (isSolved(top.state)) { - return top.totalEnergy - } - - for (let res of possibleStates(top.state)) { - let energy = top.totalEnergy + res.energy - - if (res.state in energies) { - let idx = stateHeap.data.findIndex((e) => e.state == res.state) - if (idx > -1 && energy < stateHeap.data[idx].totalEnergy) { - stateHeap.data[idx].totalEnergy = energy - stateHeap.up(idx) - } - } else { - energies[res.state] = energy - stateHeap.insert({ state: res.state, totalEnergy: energy }) - } - } - - if (i % 10000 == 0) { - console.log("iteration", i, "heap size", stateHeap.data.length, "min energy", top.totalEnergy) - } - } -} - -if (typeof window == "undefined") { - module.exports = day23 -} -function day24(input, part2) { - let lines = input.split("\n") - let ops = lines.splitOnElement("inp w").filter((e) => e.length).transpose() - - let compareVals = ops[4].map((e) => +e.split(" ").last) - let stackVals = ops[14].map((e) => +e.split(" ").last) - - let len = compareVals.length - - let stack = [] - let num = Array(len).fill() - - for (let i = 0; i < len; i++) { - if (compareVals[i] > 0) { - stack.push([i, stackVals[i]]) - } else { - num[i] = stack.pop() - num[i][1] += compareVals[i] - num[num[i][0]] = [i, -num[i][1]] - } - } - - for (let i = 0; i < len; i++) { - if (num[i][0] > i) { - num[i] = part2 ? Math.max(1, 1 + num[i][1]) : Math.min(9, 9 + num[i][1]) - } else { - num[i] = num[num[i][0]] + num[i][1] - } - } - - return num.join("") -} - -if (typeof window == "undefined") { - module.exports = day24 -} -function step(grid, dir) { - let moved = false - - grid.mapMut((e, pt, g) => { - let target = dir == 1 ? - new Point((pt.x + 1) % g.width, pt.y) : - new Point(pt.x, (pt.y + 1) % g.height) - - if (e % 3 == dir && g.get(target) % 3 == 0) { - g.set(target, dir * 3) - moved = true - return e - } else { - return e * 3 + e - } - }).mapMut((e) => ((e / 3) | 0) % 3) - - return moved -} - -function day25(input) { - let grid = Grid.fromStr(input).mapMut((e) => ".>v".indexOf(e)) - - let moved - let steps = 0 - - do { - steps++ - } while (step(grid, 1) + step(grid, 2)) - - return steps -} - -if (typeof window == "undefined") { - module.exports = day25 -} -function day2(input, part2) { - let x = 0 - let y = 0 - let a = 0 - - for (let line of input.split("\n")) { - if (!part2) { - eval(line.replace("forward", "x +=") - .replace("up", "y -=") - .replace("down", "y +=")) - } else { - eval(line.replace(/forward (.+)/, "x += $1; y += a * $1") - .replace("up", "a -=") - .replace("down", "a +=")) - } - } - - return x * y -} - -if (typeof window == "undefined") { - module.exports = day2 -} -function day3(input, part2) { - let g = Grid.fromStr(input) - - if (!part2) { - let gamma = g.getColumns().map((col) => col.mode()) - let epsilon = g.getColumns().map((col) => col.antimode()) - return parseInt(gamma.join(""), 2) * parseInt(epsilon.join(""), 2) - } else { - let oxy = g.getRows() - let co2 = g.getRows() - - for (let i = 0; i < g.width; i++) { - let oxyBit = oxy.transpose()[i].mode((a, b) => a == 1 ? -1 : 1) - let co2Bit = co2.transpose()[i].antimode((a, b) => a == 0 ? -1 : 1) - oxy = oxy.filter((e) => e[i] == oxyBit) - co2 = co2.filter((e) => e[i] == co2Bit) - } - - return parseInt(oxy[0].join(""), 2) * parseInt(co2[0].join(""), 2) - } -} - -if (typeof window == "undefined") { - module.exports = day3 -} -function day4(input, part2) { - let lines = input.split("\n") - - let seq = lines.shift().split(",").num() - let grids = lines.splitOnElement("").filter((e) => e.length).map((grid) => Grid.fromStr(grid.map((line) => line.replace(/^ /, "")).join("\n"), /\s+/).mapMut((e) => [+e, false])) - - let score - - for (let num of seq) { - for (let grid of grids) { - if (grid.won) { - continue - } - - let pt = grid.findIndex((e) => e[0] == num) - - if (pt == Point.NONE) { - continue - } - - grid.set(pt, [num, true]) - - if ([...grid.getRows(), ...grid.getColumns()].some((row) => row.every((e) => e[1]))) { - grid.won = true - - if (!score || part2) { - score = grid.findAll((e) => !e[1]).map((e) => e[0]).sum() * num - } - } - } - } - - return score -} - -if (typeof window == "undefined") { - module.exports = day4 -} -function day5(input, part2) { - let lines = input.split("\n").map((e) => e.split(" -> ").map((e) => new Point(...e.split(",").num()))) - - if (!part2) { - lines = lines.filter((e) => e[0].x == e[1].x || e[0].y == e[1].y) - } - - let visitedOnce = new Set() - let visitedTwice = new Set() - - for (let line of lines) { - let xrange = utils.signAgnosticInclusiveRange(line[0].x, line[1].x) - let yrange = utils.signAgnosticInclusiveRange(line[0].y, line[1].y) - - if (xrange.length == 1) { - xrange = yrange.map(() => line[0].x) - } - - if (yrange.length == 1) { - yrange = xrange.map(() => line[0].y) - } - - for (let i = 0; i < xrange.length; i++) { - let mask = (xrange[i] << 10) | yrange[i] - - if (visitedOnce.has(mask)) { - visitedTwice.add(mask) - } else { - visitedOnce.add(mask) - } - } - } - - return visitedTwice.size -} - -if (typeof window == "undefined") { - module.exports = day5 -} -function day6(input, part2) { - let fish = input.split(",").num() - let counts = Array(9).fill().map((_, i) => fish.count(i)) - - for (let i = 0; i < (part2 ? 256 : 80); i++) { - counts.push(counts.shift()) - counts[6] += counts[8] - } - - return counts.sum() -} - -if (typeof window == "undefined") { - module.exports = day6 -} -function day7(input, part2) { - let crabs = input.split(",").num() - - let max = crabs.max() - let costs = [] - - for (let i = 0; i < max; i++) { - costs[i] = crabs.map((e) => { - let dist = Math.abs(e - i) - return part2 ? dist * (dist + 1) / 2 : dist - }).sum() - } - - return costs.min() -} - -if (typeof window == "undefined") { - module.exports = day7 -} -function day8(input, part2) { - let lines = input.split("\n").map((e) => e.split(" ").splitOnElement("|").map((e) => e.map((e) => e.split("").sort()))) - - if (!part2) { - return lines.map((e) => e[1].count((e) => [2, 4, 3, 7].includes(e.length))).sum() - } - - let sum = 0 - - for (let line of lines) { - let samples = line.flat() - - let n1 = samples.find((e) => e.length == 2) - let n7 = samples.find((e) => e.length == 3) - let n4 = samples.find((e) => e.length == 4) - let a235 = samples.filter((e) => e.length == 5) - let a069 = samples.filter((e) => e.length == 6) - let n8 = samples.find((e) => e.length == 7) - - let n6 = a069.find((e) => n1.sub(e).length == 1) - let n9 = a069.find((e) => n4.sub(e).length == 0) - let n0 = a069.find((e) => e != n6 && e != n9) - - let n2 = a235.find((e) => n4.sub(e).length == 2) - let n3 = a235.find((e) => n1.sub(e).length == 0) - let n5 = a235.find((e) => e != n2 && e != n3) - - sum += +line[1].map((e) => [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9].findIndex((n) => n.join("") == e.join(""))).join("") - } - - return sum -} - -if (typeof window == "undefined") { - module.exports = day8 -} -function day9(input, part2) { - let grid = Grid.fromStr(input).num() - - let lows = grid.findAllIndices((e, pt, g) => g.getAdjNeighbors(pt).every((nb) => e < g.get(nb))) - - if (!part2) { - return lows.mapArr((e) => grid.get(e) + 1).sum() - } - - let sizes = lows.mapArr((low) => grid.bfs(low, (e, pt, g) => { - return e == 9 || g.get(pt.path.last) < e ? Grid.BFS_STOP : Grid.BFS_CONTINUE - }).filter((e) => e.result == Grid.BFS_CONTINUE).length).sort((a, b) => b - a) - - return sizes[0] * sizes[1] * sizes[2] -} - -if (typeof window == "undefined") { - module.exports = day9 -}