This commit is contained in:
Yanick Champoux 2023-11-30 10:13:16 -05:00
parent 602d8fd1f9
commit 95754d7b78
6 changed files with 264 additions and 261 deletions

View File

@ -3,81 +3,76 @@ import * as R from "remeda";
import { passthru } from "../08/part1.js"; import { passthru } from "../08/part1.js";
import { readFile } from "../05/part1.js"; import { readFile } from "../05/part1.js";
import V from '@yanick/vyktor'; import V from "@yanick/vyktor";
import { createLogger } from "vite"; import { createLogger } from "vite";
const readInput = (...args) => const readInput = (...args) =>
R.pipe( R.pipe(
readFile(...args), readFile(...args),
(lines) => lines.split("\n"), (lines) => lines.split("\n"),
R.compact, // remove last line R.compact, // remove last line
R.map(line => line.match(/x=-?.*?y=-?\d+/g).map(str => R.map((line) =>
str.match(/-?\d+/g).map(x => parseInt(x))).map( line
coords => V(coords) .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 puzzleInput = readInput(import.meta.url, "input");
export const sample = readInput(import.meta.url, "sample"); export const sample = readInput(import.meta.url, "sample");
/** @returns [number,number][] */ /** @returns [number,number][] */
export const mergeRanges = ( ranges ) => { export const mergeRanges = (ranges) => {
return ranges.reduce((accum, range) => {
return ranges.reduce( (accum,range) => { if (accum.length === 0) return [range];
if( accum.length === 0 ) return [ range ]; if (R.last(accum)[1] < range[0]) {
if( R.last( accum )[1] < range[0] ) { accum.push(range);
accum.push(range); } else {
} R.last(accum)[1] = Math.max(R.last(accum)[1], range[1]);
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
} }
return accum;
}, []);
}; };
export const unbeaconAtLine = targetLine => entries => R.pipe( export const spy = passthru((x) => console.log(x));
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 [ const entriesInRange = (targetLine, entries) => {
e[0].x - l, entries = R.uniqBy(
e[0].x + l ] 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, spy,
(ranges) => (ranges) =>
ranges.sort( (a,b) => { ranges.sort((a, b) => {
return (a[0] - b[0]) || (a[1]-b[1]); return a[0] - b[0] || a[1] - b[1];
} ) }),
,
spy, spy,
mergeRanges, mergeRanges,
spy, spy,
R.sumBy( range => range[1] - range[0] + 1 - entriesInRange(targetLine,entries)(range) ) R.sumBy(
); (range) =>
range[1] - range[0] + 1 - entriesInRange(targetLine, entries)(range)
)
);
export default unbeaconAtLine(2000000); export default unbeaconAtLine(2000000);

View File

@ -1,63 +1,47 @@
import * as R from "remeda"; import * as R from "remeda";
import V from '@yanick/vyktor'; import V from "@yanick/vyktor";
import { import { spy, mergeRanges } from "./part1.js";
spy,
mergeRanges
} from './part1.js';
const unbeaconAtLine = (max,targetLine,already) => entries => R.pipe( const unbeaconAtLine = (max, targetLine, already) => (entries) =>
R.pipe(
entries, entries,
R.map( ([x,y]) => [ x, x.manhattanDistance(y) ] ), R.map(([x, y]) => [x, x.manhattanDistance(y)]),
R.filter( R.filter((e) => e[0].y - e[1] <= targetLine && e[0].y + e[1] >= targetLine),
e => ( R.map((e) => {
( e[0].y - e[1] <= targetLine) && const l = e[1] - Math.abs(targetLine - e[0].y);
( e[0].y + e[1] >= targetLine)
)
),
R.map(
e => {
const l = e[1] - Math.abs(targetLine - e[0].y);
return [ return [e[0].x - l, e[0].x + l];
e[0].x - l, }),
e[0].x + l ]
}
),
//spy, //spy,
(ranges) => [ (ranges) => [
...ranges, ...already.filter( v => v.y === targetLine ).map( ...ranges,
v => [ v.x,v.x ] ...already.filter((v) => v.y === targetLine).map((v) => [v.x, v.x]),
)
], ],
(ranges) => (ranges) =>
ranges.sort( (a,b) => { ranges.sort((a, b) => {
return (a[0] - b[0]) || (a[1]-b[1]); return a[0] - b[0] || a[1] - b[1];
} ) }),
,
// spy, // spy,
R.map( R.map((range) => [Math.max(0, range[0]), Math.min(max, range[1])]),
range => [ Math.max(0,range[0]), Math.min(max,range[1])] mergeRanges
),
mergeRanges,
// spy, // spy,
// R.sumBy( range => range[1] - range[0] + 1 - entriesInRange(targetLine,entries)(range) ) // R.sumBy( range => range[1] - range[0] + 1 - entriesInRange(targetLine,entries)(range) )
); );
export const findBeacon = max => entries => { export const findBeacon = (max) => (entries) => {
const already = entries.flat();
const already = entries.flat() for (let y = 0; y <= max; y++) {
const ranges = unbeaconAtLine(max, y, already)(entries);
for (let y = 0; y <= max; y++) { if (ranges.length > 1) {
const ranges = unbeaconAtLine(max,y,already)(entries) return y + 4000000 * (ranges[0][1] + 1);
if( ranges.length > 1 ) {
return y + 4000000 * (ranges[0][1] +1);
}
// return 'bob';
// return x * 4000000 + y;
} }
}
// return 'bob';
// return x * 4000000 + y;
}
};
export default findBeacon(4000000); export default findBeacon(4000000);

View File

@ -5,25 +5,25 @@ import part1, { sample, puzzleInput, unbeaconAtLine } from "./part1.js";
import part2, { findBeacon } from "./part2.js"; import part2, { findBeacon } from "./part2.js";
describe("part 1", () => { describe("part 1", () => {
test('readInput', () => { test("readInput", () => {
expect(sample[0][0].toArray()).toEqual([2, 18]); expect(sample[0][0].toArray()).toEqual([2, 18]);
}); });
test('sample', () => { test("sample", () => {
expect(unbeaconAtLine(10)(sample)).toEqual(26); expect(unbeaconAtLine(10)(sample)).toEqual(26);
}); });
test("solution", () => { test("solution", () => {
expectSolution(part1(puzzleInput)).toEqual(5525990); expectSolution(part1(puzzleInput)).toEqual(5525990);
}); });
}); });
describe("part 2", () => { describe("part 2", () => {
test('sample', () => { test("sample", () => {
expect(findBeacon(20)(sample)).toEqual(56000011); expect(findBeacon(20)(sample)).toEqual(56000011);
}); });
test("solution", () => { test("solution", () => {
// 314 seconds! // 314 seconds!
expectSolution(part2(puzzleInput)).toEqual(11756174628223); expectSolution(part2(puzzleInput)).toEqual(11756174628223);
}); });
}); });

View File

@ -1,92 +1,98 @@
import * as R from "remeda"; import * as R from "remeda";
import u from "updeep"; import u from "updeep";
import Graph from 'graphology'; import Graph from "graphology";
import { bidirectional } from 'graphology-shortest-path'; import { bidirectional } from "graphology-shortest-path";
import { combinations, permutations } from "combinatorial-generators"; import { combinations, permutations } from "combinatorial-generators";
import { readFile } from "../05/part1.js"; import { readFile } from "../05/part1.js";
const parseLine = (line) => { const parseLine = (line) => {
const { groups } = line.match(/Valve (?<valve>..).*flow rate=(?<flow>\d+).*valves? (?<exits>.*)/); const { groups } = line.match(
return [groups.valve, { /Valve (?<valve>..).*flow rate=(?<flow>\d+).*valves? (?<exits>.*)/
exits: groups.exits.split(',').map(x => x.trim()), );
flow: parseInt(groups.flow) return [
}] groups.valve,
} {
exits: groups.exits.split(",").map((x) => x.trim()),
flow: parseInt(groups.flow),
},
];
};
const readInput = (...args) => const readInput = (...args) =>
R.pipe( R.pipe(
readFile(...args), readFile(...args),
(lines) => lines.split("\n"), (lines) => lines.split("\n"),
R.compact, // remove last line R.compact, // remove last line
R.map(parseLine), R.map(parseLine),
Object.fromEntries, Object.fromEntries
); );
export const puzzleInput = readInput(import.meta.url, "input"); export const puzzleInput = readInput(import.meta.url, "input");
export const sample = readInput(import.meta.url, "sample"); export const sample = readInput(import.meta.url, "sample");
function finalSteam(tunnels, graph, minutesLeft, possibilities, location = 'AA', activeSteam = 0) { function finalSteam(
//console.log(minutesLeft, totalSteam, itinary); tunnels,
graph,
minutesLeft,
possibilities,
location = "AA",
activeSteam = 0
) {
//console.log(minutesLeft, totalSteam, itinary);
if (minutesLeft <= 0) return 0; if (minutesLeft <= 0) return 0;
if (possibilities.length === 0) return minutesLeft * activeSteam; if (possibilities.length === 0) return minutesLeft * activeSteam;
let scores = []; let scores = [];
for (const next of possibilities) {
const path = bidirectional(graph, location, next);
for ( const next of possibilities ) {
const path = bidirectional(graph, location, next);
//console.log(path); //console.log(path);
let time = path.length; let time = path.length;
if (time >= minutesLeft) { if (time >= minutesLeft) {
scores.push( minutesLeft * activeSteam ); scores.push(minutesLeft * activeSteam);
} } else {
else { //console.log({totalSteam, time, activeSteam});
//console.log({totalSteam, time, activeSteam}); let ts = time * activeSteam;
let ts = time * activeSteam; let as = activeSteam + tunnels[next].flow;
let as = activeSteam + tunnels[next].flow;
scores.push( scores.push(
ts + finalSteam( ts +
tunnels, graph, minutesLeft - time, finalSteam(
possibilities.filter( x => x !== next ), tunnels,
next, graph,
as minutesLeft - time,
) possibilities.filter((x) => x !== next),
) next,
as
)
);
} }
}
} return Math.max(...scores);
return Math.max( ...scores );
} }
export const buildGraph = tunnels => { export const buildGraph = (tunnels) => {
const graph = new Graph(); const graph = new Graph();
Object.keys(tunnels).forEach(x => graph.addNode(x)); Object.keys(tunnels).forEach((x) => graph.addNode(x));
Object.entries(tunnels).forEach(([location, { exits }]) => { Object.entries(tunnels).forEach(([location, { exits }]) => {
exits.forEach(exit => graph.addEdge(location, exit)); exits.forEach((exit) => graph.addEdge(location, exit));
}); });
return graph;
return graph;
}; };
export default (tunnels) => { export default (tunnels) => {
const possibilities = Object.keys(tunnels).filter((k) => tunnels[k].flow);
const possibilities = const graph = buildGraph(tunnels);
Object.keys(tunnels).filter(
k => tunnels[k].flow
);
const graph = buildGraph(tunnels);
return finalSteam(tunnels, graph, 30, possibilities, 'AA', 0, 0)
return finalSteam(tunnels, graph, 30, possibilities, "AA", 0, 0);
}; };

View File

@ -1,86 +1,105 @@
import { bidirectional } from 'graphology-shortest-path'; import { bidirectional } from "graphology-shortest-path";
import * as R from "remeda"; import * as R from "remeda";
import u from 'updeep'; import u from "updeep";
import { buildGraph } from './part1.js'; import { buildGraph } from "./part1.js";
const findMaxSteam = (tunnels,graph, minutesLeft, unopened, opened, activeSteam, peeps ) => { const findMaxSteam = (
tunnels,
graph,
minutesLeft,
unopened,
opened,
activeSteam,
peeps
) => {
console.log({ unopened, activeSteam, minutesLeft });
console.log({ unopened, activeSteam, minutesLeft}); let next = R.sortBy(peeps, R.prop("eta"));
let next = R.sortBy(peeps, R.prop('eta')); if (next[0].eta > minutesLeft) {
return minutesLeft * activeSteam;
}
if( next[0].eta > minutesLeft ) { const delta = next[0].eta;
return minutesLeft * activeSteam; 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;
const delta = next[0].eta; next = u.updateIn(
const location = next[0].destination; "0",
{
location,
eta: path.length,
destination,
},
next
);
const base = delta * activeSteam; scores.push(
minutesLeft -= delta; findMaxSteam(
tunnels,
graph,
minutesLeft,
unopened.filter((x) => x !== destination),
opened,
activeSteam,
next
)
);
}
activeSteam += tunnels[ next[0].destination ].flow; if (nothing) {
opened = [ ...opened, next[0].destination ]; next = u.updateIn(
"0",
{
location,
eta: 999,
destination: "END",
},
next
);
next = u.updateIn( '1.eta', eta => eta - delta, next ); scores.push(
findMaxSteam(
tunnels,
graph,
minutesLeft,
unopened,
opened,
activeSteam,
next
)
);
}
const scores = []; return base + Math.max(...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) => { export default (tunnels) => {
const possibilities = Object.keys(tunnels).filter((k) => tunnels[k].flow);
const possibilities = const graph = buildGraph(tunnels);
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 },
])
return findMaxSteam(tunnels, graph, 26, possibilities, [], 0, [
{ destination: "AA", eta: 0 },
{ destination: "AA", eta: 0 },
]);
}; };

View File

@ -5,21 +5,20 @@ import part1, { sample, puzzleInput } from "./part1.js";
import part2 from "./part2.js"; import part2 from "./part2.js";
describe("part 1", () => { describe("part 1", () => {
test( "input", ()=> { test("input", () => {
expect(sample).toMatchObject({AA: { expect(sample).toMatchObject({
exists: [ AA: {
'DD', 'II', 'BB', exists: ["DD", "II", "BB"],
], flow: 0,
flow: 0, },
}}) });
});
} )
test("sample", () => { test("sample", () => {
expect(part1(sample)).toEqual(1651); expect(part1(sample)).toEqual(1651);
}); });
test("solution", () => { test("solution", () => {
const r= part1(puzzleInput); const r = part1(puzzleInput);
expect(r).toBeGreaterThan(707); expect(r).toBeGreaterThan(707);
expectSolution(part1(puzzleInput)).toEqual(1871); expectSolution(part1(puzzleInput)).toEqual(1871);
}); });
}); });