Merge branch '2022-16'
This commit is contained in:
commit
602d8fd1f9
83
2022/15/part1.js
Normal file
83
2022/15/part1.js
Normal file
@ -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);
|
63
2022/15/part2.js
Normal file
63
2022/15/part2.js
Normal file
@ -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);
|
14
2022/15/sample
Normal file
14
2022/15/sample
Normal file
@ -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
|
29
2022/15/test.js
Normal file
29
2022/15/test.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
4
2022/16/fast.js
Normal file
4
2022/16/fast.js
Normal file
@ -0,0 +1,4 @@
|
||||
import part1, { sample, puzzleInput } from "./part1.js";
|
||||
import part2 from "./part2.js";
|
||||
|
||||
console.log(part2(puzzleInput));
|
92
2022/16/part1.js
Normal file
92
2022/16/part1.js
Normal file
@ -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 (?<valve>..).*flow rate=(?<flow>\d+).*valves? (?<exits>.*)/);
|
||||
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)
|
||||
|
||||
};
|
86
2022/16/part2.js
Normal file
86
2022/16/part2.js
Normal file
@ -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 },
|
||||
])
|
||||
|
||||
};
|
10
2022/16/sample
Normal file
10
2022/16/sample
Normal file
@ -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
|
34
2022/16/test.js
Normal file
34
2022/16/test.js
Normal file
@ -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");
|
||||
});
|
||||
});
|
10
package.json
10
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"
|
||||
|
Loading…
Reference in New Issue
Block a user