diff --git a/2022/12/part1.js b/2022/12/part1.js index c78f7d4..3ea090a 100644 --- a/2022/12/part1.js +++ b/2022/12/part1.js @@ -3,14 +3,14 @@ import Victor from "@a-robu/victor"; import { readFile } from "../05/part1.js"; -const V = (x,y) => new Victor(x,y); +const V = (x, y) => new Victor(x, y); const readInput = (...args) => R.pipe( readFile(...args), (lines) => lines.split("\n"), R.compact, // remove last line - R.map((line) => line.split("").map( c => c ==='S' ? '`' : c === 'E' ? '{' : c )) + R.map((l) => l.split("")) ); export const puzzleInput = readInput(import.meta.url, "input"); @@ -19,76 +19,81 @@ export const sample = readInput(import.meta.url, "sample"); export const findStart = (topoMap) => { let y; let x = R.findIndex(topoMap, (row) => { - let z = row.indexOf("`"); + let z = row.indexOf("S"); if (z === -1) return false; y = z; return true; }); - return V(x,y); + return V(x, y); }; -const directions = [ [0,1], [0,-1],[1,0],[-1,0] ].map( - d => V(...d) -); +export const findEnd = (topoMap) => { + let y; + let x = R.findIndex(topoMap, (row) => { + let z = row.indexOf("E"); + if (z === -1) return false; + y = z; + return true; + }); + return V(x, y); +}; -const outOfMap = (maxX,maxY) => (loc) => { - if( Math.min( loc.x, loc.y ) < 0 ) return true; - if( loc.x >= maxX ) return true; - if( loc.y >= maxY ) return true; - return false; -} +const directions = [ + [0, 1], + [0, -1], + [1, 0], + [-1, 0], +].map((d) => V(...d)); -const isReachable = (topoMap,pos) => { - const baseline = topoMap[pos.x][pos.y].charCodeAt(0); +const outOfMap = (maxX, maxY) => (loc) => { + if (Math.min(loc.x, loc.y) < 0) return true; + if (loc.x >= maxX) return true; + if (loc.y >= maxY) return true; + return false; +}; - return (next) => { - return [0,1].includes( - topoMap[next.x][next.y].charCodeAt(0) - baseline - ) - } -} +const isReachable = (topoMap, pos) => { + const baseline = topoMap[pos.x][pos.y].charCodeAt(0); + + return (next) => { + return topoMap[next.x][next.y].charCodeAt(0) - baseline <= 1; + }; +}; function findShortestPath(topoMap) { + const initial = findStart(topoMap); + initial.steps = 0; + const final = findEnd(topoMap); - const initial = findStart(topoMap); - initial.steps = 0; - initial.beenThere = []; + topoMap = topoMap.map((line) => + line.map((c) => c.replace("S", "a").replace("E", "z")) + ); - const potentials = [ initial ]; + const potentials = [initial]; - let bestSoFar; + const beenThere = topoMap.map((line) => line.map(() => 9999)); - const oom = outOfMap(topoMap.length,topoMap[0].length ); + const oom = outOfMap(topoMap.length, topoMap[0].length); - let failsafe = 100000; - while( potentials.length > 0 ) { - // if(! failsafe--) return; - const pos = potentials.pop(); // depth-first - //console.log(pos.steps, bestSoFar); + let failsafe = 10; //topoMap.length*topoMap[0].length ; + while (potentials.length > 0) { + // if(! failsafe--) return; + const pos = potentials.shift(); // depth-first - if( bestSoFar && (bestSoFar <= pos.steps) ) continue; + if (beenThere[pos.x][pos.y] <= pos.steps) continue; + beenThere[pos.x][pos.y] = pos.steps; - if( topoMap[pos.x][pos.y] === '{' ) { - bestSoFar = pos.steps; - continue; - } + const next = directions + .map((d) => d.clone().add(pos)) + .filter(R.isNot(oom)) + .filter(isReachable(topoMap, pos)); - const next = directions.map( - d => d.clone().add(pos) - ).filter( R.isNot(oom )) - .filter( isReachable(topoMap,pos) ) - .filter( next => !pos.beenThere.includes( next.toArray().join(',') ) ); + next.forEach((n) => (n.steps = pos.steps + 1)); - next.forEach( n => n.steps = pos.steps + 1 ); - next.forEach( n => n.beenThere = [ - ...pos.beenThere, pos.toArray().join(',') - ] ); - - potentials.push(...next); - } - - return bestSoFar; + potentials.push(...next); + } + return beenThere[final.x][final.y]; } export default findShortestPath; diff --git a/2022/12/part2.js b/2022/12/part2.js index 10fc179..1b1969d 100644 --- a/2022/12/part2.js +++ b/2022/12/part2.js @@ -1,4 +1,94 @@ import * as R from "remeda"; +import Victor from "@a-robu/victor"; +import { readFile } from "../05/part1.js"; -export default () => {}; +const V = (x, y) => new Victor(x, y); + +export const findAs = (topoMap) => { + const locations = []; + + topoMap.forEach((line, x) => + line.forEach((v, y) => { + if (v === "a") locations.push([x, y]); + }) + ); + + return locations; +}; + +export const findEnd = (topoMap) => { + let y; + let x = R.findIndex(topoMap, (row) => { + let z = row.indexOf("E"); + if (z === -1) return false; + y = z; + return true; + }); + return V(x, y); +}; + +const directions = [ + [0, 1], + [0, -1], + [1, 0], + [-1, 0], +].map((d) => V(...d)); + +const outOfMap = (maxX, maxY) => (loc) => { + if (Math.min(loc.x, loc.y) < 0) return true; + if (loc.x >= maxX) return true; + if (loc.y >= maxY) return true; + return false; +}; + +const isReachable = (topoMap, pos) => { + const baseline = topoMap[pos.x][pos.y].charCodeAt(0); + + return (next) => { + return baseline - topoMap[next.x][next.y].charCodeAt(0) <= 1; + }; +}; + +function findShortestPath(topoMap) { + const final = findEnd(topoMap); + final.steps = 0; + + topoMap = topoMap.map((line) => + line.map((c) => c.replace("S", "a").replace("E", "z")) + ); + + const initial = findAs(topoMap); + + const potentials = [final]; + + const beenThere = topoMap.map((line) => line.map(() => 9999)); + + const oom = outOfMap(topoMap.length, topoMap[0].length); + + let failsafe = 10; //topoMap.length*topoMap[0].length ; + while (potentials.length > 0) { + // if(! failsafe--) return; + const pos = potentials.shift(); // depth-first + + if (beenThere[pos.x][pos.y] <= pos.steps) continue; + beenThere[pos.x][pos.y] = pos.steps; + + const next = directions + .map((d) => d.clone().add(pos)) + .filter(R.isNot(oom)) + .filter(isReachable(topoMap, pos)); + + next.forEach((n) => (n.steps = pos.steps + 1)); + + potentials.push(...next); + } + + return R.pipe( + initial, + R.map(([x, y]) => beenThere[x][y]), + R.minBy(R.identity) + ); +} + +export default findShortestPath; diff --git a/2022/12/test.js b/2022/12/test.js index 19deda3..87e8f6c 100644 --- a/2022/12/test.js +++ b/2022/12/test.js @@ -5,22 +5,24 @@ import part1, { findStart, sample, puzzleInput } from "./part1.js"; import part2 from "./part2.js"; describe("part 1", () => { - test( "findStart", () => { - expect( findStart(sample).toArray() ).toEqual( - [0,0] - ) - - }); - test( "sample", async () => { - expect(part1(sample)).toEqual(31); - }); - test.only("solution", () => { - expectSolution(part1(puzzleInput)).toEqual("TODO"); + test("findStart", () => { + expect(findStart(sample).toArray()).toEqual([0, 0]); + }); + test("sample", () => { + expect(part1(sample)).toEqual(31); + }); + test("solution", () => { + const r = part1(puzzleInput); + expect(r).toBeLessThan(9999); + expectSolution(r).toEqual(361); }); }); -describe("part 2", () => { - test.todo("solution", () => { - expectSolution(part2(puzzleInput)).toEqual("TODO"); +describe.only("part 2", () => { + test("sample", () => { + expect(part2(sample)).toEqual(29); + }); + test("solution", () => { + expectSolution(part2(puzzleInput)).toEqual(354); }); }); diff --git a/2022/13/part1.js b/2022/13/part1.js new file mode 100644 index 0000000..c19ce7c --- /dev/null +++ b/2022/13/part1.js @@ -0,0 +1,42 @@ +import * as R from "remeda"; + +import { readFile } from "../05/part1.js"; + +const readInput = (...args) => + R.pipe( + readFile(...args), + (lines) => lines.split("\n\n"), + R.compact, // remove last line + R.map((block) => block.split("\n").map((line) => eval(line))) + ); + +export const puzzleInput = readInput(import.meta.url, "input"); +export const sample = readInput(import.meta.url, "sample"); + +export function isLessThan(x, y) { + if ([x, y].every(R.isNumber)) return x < y ? -1 : x == y ? 0 : 1; + + if ([x, y].every(R.isArray)) { + for (const [a, b] of R.zip(x, y)) { + const res = isLessThan(a, b); + if (res !== 0) return res; + } + if (x.length === y.length) return 0; + return x.length < y.length ? -1 : 1; + } + + return isLessThan(...[x, y].map((z) => (R.isNumber(z) ? [z] : z))); +} + +const passthru = (x) => (arg) => { + x(arg); + return arg; +}; + +export default R.createPipe( + R.map(([x, y]) => isLessThan(x, y)), + (results) => results.map((value, index) => ({ value, index: index + 1 })), + R.filter(({ value }) => value !== 1), + R.map(R.prop("index")), + (results) => results.reduce((a, b) => a + b) +); diff --git a/2022/13/part2.js b/2022/13/part2.js new file mode 100644 index 0000000..79f8c62 --- /dev/null +++ b/2022/13/part2.js @@ -0,0 +1,14 @@ +import * as R from "remeda"; +import { isLessThan } from "./part1"; + +const dividers = [[[2]], [[6]]]; +export default R.createPipe( + R.flatten, + R.concat(dividers), + (packets) => packets.sort(isLessThan), + (packets) => + packets.map((p, i) => ({ i: i + 1, divider: dividers.includes(p) })), + R.filter(({ divider }) => divider), + R.map(R.prop("i")), + (v) => v.reduce((a, b) => a * b) +); diff --git a/2022/13/sample b/2022/13/sample new file mode 100644 index 0000000..af73fbb --- /dev/null +++ b/2022/13/sample @@ -0,0 +1,23 @@ +[1,1,3,1,1] +[1,1,5,1,1] + +[[1],[2,3,4]] +[[1],4] + +[9] +[[8,7,6]] + +[[4,4],4,4] +[[4,4],4,4,4] + +[7,7,7,7] +[7,7,7] + +[] +[3] + +[[[]]] +[[]] + +[1,[2,[3,[4,[5,6,7]]]],8,9] +[1,[2,[3,[4,[5,6,0]]]],8,9] diff --git a/2022/13/test.js b/2022/13/test.js new file mode 100644 index 0000000..7fb35cb --- /dev/null +++ b/2022/13/test.js @@ -0,0 +1,36 @@ +import { test, expect, describe } from "vitest"; + +import { expectSolution } from "../01/main.js"; +import part1, { sample, puzzleInput, isLessThan } from "./part1.js"; +import part2 from "./part2.js"; + +describe("part 1", () => { + test("readInput", () => { + expect(sample[0][0]).toEqual([1, 1, 3, 1, 1]); + }); + test("lessThan", () => { + expect(isLessThan(1, 2)).toBe(-1); + expect(isLessThan(2, 1)).toBe(1); + expect(isLessThan(2, 2)).toBe(0); + expect(isLessThan(2, [1])).toBe(1); + expect(isLessThan([1], [2])).toBe(-1); + }); + test("sample", () => { + expect(part1(sample)).toEqual(13); + }); + test("solution", () => { + const r = part1(puzzleInput); + expect(r).toBeGreaterThan(435); + expect(r).toBeLessThan(10980); + expectSolution(r).toEqual(5806); + }); +}); + +describe("part 2", () => { + test("sample", () => { + expect(part2(sample)).toEqual(140); + }); + test("solution", () => { + expectSolution(part2(puzzleInput)).toEqual(23600); + }); +}); diff --git a/2022/14/part1.js b/2022/14/part1.js new file mode 100644 index 0000000..be5a501 --- /dev/null +++ b/2022/14/part1.js @@ -0,0 +1,83 @@ +import * as R from "remeda"; +import Victor from "@a-robu/victor"; + +import { readFile } from "../05/part1.js"; + +export const V = (...args) => new Victor(...args); +const parseLine = (line) => line.match(/[\d,]+/g).map((x) => eval(`[${x}]`)); + +const readInput = (...args) => + R.pipe( + readFile(...args), + (lines) => lines.split("\n"), + R.compact, // remove last line + R.map(parseLine) + ); + +export const puzzleInput = readInput(import.meta.url, "input"); +export const sample = readInput(import.meta.url, "sample"); + +export function genCave(rocks) { + const cave = {}; + for (const rock of rocks) { + const points = rock.map(Victor.fromArray); + let from = points.shift(); + + while (points.length) { + /** @type Victor */ + const to = points.shift(); + const direction = to.clone().subtract(from).normalize(); + + R.times(to.clone().subtract(from).length() + 1, () => { + if (!cave[from.y]) cave[from.y] = {}; + cave[from.y][from.x] = "#"; + from.add(direction); + }); + + from = to; + } + } + return cave; +} + +function pourSand(cave) { + cave = R.clone(cave); + + let sand = 0; + + /** @type Victor */ + let current; + + const maxDepth = R.pipe( + cave, + R.keys, + R.map((x) => parseInt(x)), + R.maxBy(R.identity) + ); + + console.log({ maxDepth }); + + while (true) { + if (!current) current = V(500, 0); + if (current.y > maxDepth) return sand; + + const next = [ + [0, 1], + [-1, 1], + [1, 1], + ] + .map((args) => V(...args)) + .map((v) => v.add(current)) + .find((v) => !(cave[v.y] && cave[v.y][v.x])); + if (next) { + current = next; + } else { + if (!cave[current.y]) cave[current.y] = {}; + cave[current.y][current.x] = "o"; + sand++; + current = null; + } + } +} + +export default R.createPipe(genCave, pourSand); diff --git a/2022/14/part2.js b/2022/14/part2.js new file mode 100644 index 0000000..438239b --- /dev/null +++ b/2022/14/part2.js @@ -0,0 +1,53 @@ +import * as R from "remeda"; +import { genCave, V } from "./part1.js"; + +function pourSand(cave) { + cave = R.clone(cave); + + let sand = 0; + + /** @type Victor */ + let current; + + const maxDepth = + R.pipe( + cave, + R.keys, + R.map((x) => parseInt(x)), + R.maxBy(R.identity) + ) + 1; + + while (true) { + if (!current) current = V(500, 0); + + if (current.y === maxDepth) { + if (!cave[current.y]) cave[current.y] = {}; + cave[current.y][current.x] = "o"; + sand++; + current = null; + continue; + } + + const next = [ + [0, 1], + [-1, 1], + [1, 1], + ] + .map((args) => V(...args)) + .map((v) => v.add(current)) + .find((v) => !(cave[v.y] && cave[v.y][v.x])); + + if (next) { + current = next; + } else { + if (current.y === 0) return 1 + sand; + + if (!cave[current.y]) cave[current.y] = {}; + cave[current.y][current.x] = "o"; + sand++; + current = null; + } + } +} + +export default R.createPipe(genCave, pourSand); diff --git a/2022/14/sample b/2022/14/sample new file mode 100644 index 0000000..4e87bb5 --- /dev/null +++ b/2022/14/sample @@ -0,0 +1,2 @@ +498,4 -> 498,6 -> 496,6 +503,4 -> 502,4 -> 502,9 -> 494,9 diff --git a/2022/14/test.js b/2022/14/test.js new file mode 100644 index 0000000..997ba5f --- /dev/null +++ b/2022/14/test.js @@ -0,0 +1,43 @@ +import { test, expect, describe } from "vitest"; + +import { expectSolution } from "../01/main.js"; +import part1, { genCave, sample, puzzleInput } from "./part1.js"; +import part2 from "./part2.js"; + +describe("part 1", () => { + test("readInput", () => { + expect(sample).toEqual([ + [ + [498, 4], + [498, 6], + [496, 6], + ], + [ + [503, 4], + [502, 4], + [502, 9], + [494, 9], + ], + ]); + }); + test("genCave", () => { + expect(genCave(sample)).toMatchObject({ + 4: { 498: "#" }, + }); + }); + test("sample", () => { + expect(part1(sample)).toEqual(24); + }); + test("solution", () => { + expectSolution(part1(puzzleInput)).toEqual(832); + }); +}); + +describe.only("part 2", () => { + test("sample", () => { + expect(part2(sample)).toEqual(93); + }); + test("solution", () => { + expectSolution(part2(puzzleInput)).toEqual(27601); + }); +}); diff --git a/_templates/day/new/part1.ejs b/_templates/day/new/part1.ejs index 33430d6..8a664ec 100644 --- a/_templates/day/new/part1.ejs +++ b/_templates/day/new/part1.ejs @@ -6,9 +6,11 @@ import * as R from "remeda"; import { readFile } from "../05/part1.js"; const readInput = (...args) => - readFile(...args) - .split("\n") - .filter((x) => x); + R.pipe( + readFile(...args), + (lines) => lines.split("\n"), + R.compact, // remove last line + ); export const puzzleInput = readInput(import.meta.url, "input"); export const sample = readInput(import.meta.url, "sample"); diff --git a/_templates/package.json b/_templates/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/_templates/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/package.json b/package.json index 232dcb9..86b09f3 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "type": "module", "name": "2022", "version": "1.0.0", "description": "", @@ -16,6 +17,10 @@ "memoizerific": "^1.11.3", "prettier": "^2.8.0", "remeda": "^1.3.0", - "vitest": "^0.25.3" + "vite": "^4.0.1", + "vitest": "0.25.7" + }, + "devDependencies": { + "@vitest/ui": "^0.25.8" } }