diff --git a/2022/15/part1.js b/2022/15/part1.js new file mode 100644 index 0000000..a196927 --- /dev/null +++ b/2022/15/part1.js @@ -0,0 +1,83 @@ +import * as R from "remeda"; + +import { passthru } from "../08/part1.js"; +import { readFile } from "../05/part1.js"; + +import V from '@yanick/vyktor'; +import { createLogger } from "vite"; + +const readInput = (...args) => + R.pipe( + readFile(...args), + (lines) => lines.split("\n"), + R.compact, // remove last line + R.map(line => line.match(/x=-?.*?y=-?\d+/g).map(str => + str.match(/-?\d+/g).map(x => parseInt(x))).map( + coords => V(coords) + ) + )); + +export const puzzleInput = readInput(import.meta.url, "input"); +export const sample = readInput(import.meta.url, "sample"); + +/** @returns [number,number][] */ +export const mergeRanges = ( ranges ) => { + +return ranges.reduce( (accum,range) => { + if( accum.length === 0 ) return [ range ]; + if( R.last( accum )[1] < range[0] ) { + accum.push(range); + } + else { + R.last(accum)[1] = Math.max( + R.last(accum)[1], range[1] + ) + } + return accum; + }, [] ); +} + +export const spy = passthru( x => console.log(x) ); + +const entriesInRange = (targetLine,entries) => { + + entries = R.uniqBy(entries.map(([_,beacon])=>beacon).filter( beacon => beacon?.y === targetLine ), (x) => x.toString()); + + return range => { + return entries.filter( + entry => (entry.x >= range[0]) && (entry.x <= range[1]) + ).length + } +}; + +export const unbeaconAtLine = targetLine => entries => R.pipe( + entries, + R.map( ([x,y]) => [ x, x.manhattanDistance(y) ] ), + R.filter( +e => ( + ( e[0].y - e[1] <= targetLine) && + ( e[0].y + e[1] >= targetLine) + ) + ), + R.map( + e => { + const l = e[1] - Math.abs(targetLine - e[0].y); + + return [ + e[0].x - l, + e[0].x + l ] + } + ), + spy, + (ranges) => + ranges.sort( (a,b) => { + return (a[0] - b[0]) || (a[1]-b[1]); + } ) + , + spy, + mergeRanges, + spy, + R.sumBy( range => range[1] - range[0] + 1 - entriesInRange(targetLine,entries)(range) ) + ); + +export default unbeaconAtLine(2000000); diff --git a/2022/15/part2.js b/2022/15/part2.js new file mode 100644 index 0000000..543b6bf --- /dev/null +++ b/2022/15/part2.js @@ -0,0 +1,63 @@ +import * as R from "remeda"; +import V from '@yanick/vyktor'; + +import { + spy, +mergeRanges +} from './part1.js'; + +const unbeaconAtLine = (max,targetLine,already) => entries => R.pipe( + entries, + R.map( ([x,y]) => [ x, x.manhattanDistance(y) ] ), + R.filter( +e => ( + ( e[0].y - e[1] <= targetLine) && + ( e[0].y + e[1] >= targetLine) + ) + ), + R.map( + e => { + const l = e[1] - Math.abs(targetLine - e[0].y); + + return [ + e[0].x - l, + e[0].x + l ] + } + ), + //spy, + (ranges) => [ + ...ranges, ...already.filter( v => v.y === targetLine ).map( + v => [ v.x,v.x ] + ) + ], + (ranges) => + ranges.sort( (a,b) => { + return (a[0] - b[0]) || (a[1]-b[1]); + } ) + , + // spy, + R.map( + range => [ Math.max(0,range[0]), Math.min(max,range[1])] + ), + mergeRanges, + // spy, + // R.sumBy( range => range[1] - range[0] + 1 - entriesInRange(targetLine,entries)(range) ) + ); + +export const findBeacon = max => entries => { + + const already = entries.flat() + + for (let y = 0; y <= max; y++) { + const ranges = unbeaconAtLine(max,y,already)(entries) + if( ranges.length > 1 ) { + return y + 4000000 * (ranges[0][1] +1); + } + +// return 'bob'; + + // return x * 4000000 + y; + } +} + +export default findBeacon(4000000); diff --git a/2022/15/sample b/2022/15/sample new file mode 100644 index 0000000..a612424 --- /dev/null +++ b/2022/15/sample @@ -0,0 +1,14 @@ +Sensor at x=2, y=18: closest beacon is at x=-2, y=15 +Sensor at x=9, y=16: closest beacon is at x=10, y=16 +Sensor at x=13, y=2: closest beacon is at x=15, y=3 +Sensor at x=12, y=14: closest beacon is at x=10, y=16 +Sensor at x=10, y=20: closest beacon is at x=10, y=16 +Sensor at x=14, y=17: closest beacon is at x=10, y=16 +Sensor at x=8, y=7: closest beacon is at x=2, y=10 +Sensor at x=2, y=0: closest beacon is at x=2, y=10 +Sensor at x=0, y=11: closest beacon is at x=2, y=10 +Sensor at x=20, y=14: closest beacon is at x=25, y=17 +Sensor at x=17, y=20: closest beacon is at x=21, y=22 +Sensor at x=16, y=7: closest beacon is at x=15, y=3 +Sensor at x=14, y=3: closest beacon is at x=15, y=3 +Sensor at x=20, y=1: closest beacon is at x=15, y=3 diff --git a/2022/15/test.js b/2022/15/test.js new file mode 100644 index 0000000..e53af8b --- /dev/null +++ b/2022/15/test.js @@ -0,0 +1,29 @@ +import { test, expect, describe } from "vitest"; + +import { expectSolution } from "../01/main.js"; +import part1, { sample, puzzleInput, unbeaconAtLine } from "./part1.js"; +import part2, { findBeacon } from "./part2.js"; + +describe("part 1", () => { + test('readInput', () => { + expect(sample[0][0].toArray()).toEqual([2, 18]); + }); + + test('sample', () => { + expect(unbeaconAtLine(10)(sample)).toEqual(26); + }); + + test("solution", () => { + expectSolution(part1(puzzleInput)).toEqual(5525990); + }); +}); + +describe("part 2", () => { + test('sample', () => { + expect(findBeacon(20)(sample)).toEqual(56000011); + }); + test("solution", () => { + // 314 seconds! + expectSolution(part2(puzzleInput)).toEqual(11756174628223); + }); +}); diff --git a/2022/16/fast.js b/2022/16/fast.js new file mode 100644 index 0000000..5357596 --- /dev/null +++ b/2022/16/fast.js @@ -0,0 +1,4 @@ +import part1, { sample, puzzleInput } from "./part1.js"; +import part2 from "./part2.js"; + +console.log(part2(puzzleInput)); diff --git a/2022/16/part1.js b/2022/16/part1.js new file mode 100644 index 0000000..859c0be --- /dev/null +++ b/2022/16/part1.js @@ -0,0 +1,92 @@ +import * as R from "remeda"; +import u from "updeep"; + +import Graph from 'graphology'; +import { bidirectional } from 'graphology-shortest-path'; + +import { combinations, permutations } from "combinatorial-generators"; + +import { readFile } from "../05/part1.js"; + +const parseLine = (line) => { + const { groups } = line.match(/Valve (?..).*flow rate=(?\d+).*valves? (?.*)/); + return [groups.valve, { + exits: groups.exits.split(',').map(x => x.trim()), + flow: parseInt(groups.flow) + }] +} +const readInput = (...args) => + R.pipe( + readFile(...args), + (lines) => lines.split("\n"), + R.compact, // remove last line + R.map(parseLine), + Object.fromEntries, + ); + +export const puzzleInput = readInput(import.meta.url, "input"); +export const sample = readInput(import.meta.url, "sample"); + +function finalSteam(tunnels, graph, minutesLeft, possibilities, location = 'AA', activeSteam = 0) { + //console.log(minutesLeft, totalSteam, itinary); + + if (minutesLeft <= 0) return 0; + + if (possibilities.length === 0) return minutesLeft * activeSteam; + + let scores = []; + + for ( const next of possibilities ) { + const path = bidirectional(graph, location, next); + + //console.log(path); + + let time = path.length; + if (time >= minutesLeft) { + scores.push( minutesLeft * activeSteam ); + } + else { + //console.log({totalSteam, time, activeSteam}); + let ts = time * activeSteam; + let as = activeSteam + tunnels[next].flow; + + scores.push( + ts + finalSteam( + tunnels, graph, minutesLeft - time, + possibilities.filter( x => x !== next ), + next, + as + ) + ) + } + + } + + return Math.max( ...scores ); +} + +export const buildGraph = tunnels => { + const graph = new Graph(); + Object.keys(tunnels).forEach(x => graph.addNode(x)); + + Object.entries(tunnels).forEach(([location, { exits }]) => { + exits.forEach(exit => graph.addEdge(location, exit)); + }); + + + return graph; + +}; + +export default (tunnels) => { + + const possibilities = + Object.keys(tunnels).filter( + k => tunnels[k].flow + ); + + const graph = buildGraph(tunnels); + + return finalSteam(tunnels, graph, 30, possibilities, 'AA', 0, 0) + +}; diff --git a/2022/16/part2.js b/2022/16/part2.js new file mode 100644 index 0000000..1c649cb --- /dev/null +++ b/2022/16/part2.js @@ -0,0 +1,86 @@ +import { bidirectional } from 'graphology-shortest-path'; +import * as R from "remeda"; +import u from 'updeep'; + +import { buildGraph } from './part1.js'; + +const findMaxSteam = (tunnels,graph, minutesLeft, unopened, opened, activeSteam, peeps ) => { + + console.log({ unopened, activeSteam, minutesLeft}); + + let next = R.sortBy(peeps, R.prop('eta')); + + if( next[0].eta > minutesLeft ) { + return minutesLeft * activeSteam; + } + + const delta = next[0].eta; + const location = next[0].destination; + + const base = delta * activeSteam; + minutesLeft -= delta; + + activeSteam += tunnels[ next[0].destination ].flow; + opened = [ ...opened, next[0].destination ]; + + next = u.updateIn( '1.eta', eta => eta - delta, next ); + + const scores = []; + + + + + let nothing = true; + for ( const destination of unopened ) { + const path = bidirectional(graph, location, destination ); + + if( path.length > minutesLeft) { + continue; + } + nothing = false; + + next = u.updateIn( '0', { + location, + eta: path.length, + destination + },next); + + scores.push( findMaxSteam(tunnels,graph,minutesLeft, + unopened.filter( x => x !== destination ), + opened, activeSteam, next + ) ); + } + + if( nothing ) { + next = u.updateIn( '0', { + location, + eta: 999, + destination: 'END', + },next); + + scores.push( findMaxSteam(tunnels,graph,minutesLeft, + unopened, + opened, activeSteam, next + ) ); + } + + return base + Math.max( ...scores ); + + +} + +export default (tunnels) => { + + const possibilities = + Object.keys(tunnels).filter( + k => tunnels[k].flow + ); + + const graph = buildGraph(tunnels); + + return findMaxSteam(tunnels, graph, 26, possibilities,[],0, [ + { destination: 'AA', eta: 0 }, + { destination: 'AA', eta: 0 }, + ]) + +}; diff --git a/2022/16/sample b/2022/16/sample new file mode 100644 index 0000000..9f30acc --- /dev/null +++ b/2022/16/sample @@ -0,0 +1,10 @@ +Valve AA has flow rate=0; tunnels lead to valves DD, II, BB +Valve BB has flow rate=13; tunnels lead to valves CC, AA +Valve CC has flow rate=2; tunnels lead to valves DD, BB +Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE +Valve EE has flow rate=3; tunnels lead to valves FF, DD +Valve FF has flow rate=0; tunnels lead to valves EE, GG +Valve GG has flow rate=0; tunnels lead to valves FF, HH +Valve HH has flow rate=22; tunnel leads to valve GG +Valve II has flow rate=0; tunnels lead to valves AA, JJ +Valve JJ has flow rate=21; tunnel leads to valve II diff --git a/2022/16/test.js b/2022/16/test.js new file mode 100644 index 0000000..44c122e --- /dev/null +++ b/2022/16/test.js @@ -0,0 +1,34 @@ +import { test, expect, describe } from "vitest"; + +import { expectSolution } from "../01/main.js"; +import part1, { sample, puzzleInput } from "./part1.js"; +import part2 from "./part2.js"; + +describe("part 1", () => { + test( "input", ()=> { + expect(sample).toMatchObject({AA: { + exists: [ + 'DD', 'II', 'BB', + ], + flow: 0, + }}) + + } ) + test("sample", () => { + expect(part1(sample)).toEqual(1651); + }); + test("solution", () => { + const r= part1(puzzleInput); + expect(r).toBeGreaterThan(707); + expectSolution(part1(puzzleInput)).toEqual(1871); + }); +}); + +describe.only("part 2", () => { + test.skip("sample", () => { + expect(part2(sample)).toEqual(1707); + }); + test("solution", () => { + expectSolution(part2(puzzleInput)).toEqual("TODO"); + }); +}); diff --git a/package.json b/package.json index 86b09f3..b49046c 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,19 @@ "license": "ISC", "dependencies": { "@a-robu/victor": "^2.2.2", + "@yanick/vyktor": "link:../vyktor", + "combinatorial-generators": "^1.1.2", "debug": "^4.3.4", "fs-extra": "^11.1.0", + "graphology": "^0.25.1", + "graphology-shortest-path": "^2.0.1", + "graphology-types": "^0.24.5", "memoizerific": "^1.11.3", "prettier": "^2.8.0", "remeda": "^1.3.0", - "vite": "^4.0.1", - "vitest": "0.25.7" + "updeep": "^1.2.1", + "vite": "4.0.3", + "vitest": "0.26.3" }, "devDependencies": { "@vitest/ui": "^0.25.8"