|
1 | 1 | const opposite = { up: "down", down: "up", left: "right", right: "left" }; |
2 | 2 |
|
3 | | -function solve(input, part2 = false) { |
| 3 | +function parse(input) { |
4 | 4 | let map = input.split("\n").map(line => line.split("")); |
5 | 5 | let sy = map.findIndex(line => line.includes("S")); |
6 | 6 | let sx = map[sy].indexOf("S"); |
7 | 7 | let ey = map.findIndex(line => line.includes("E")); |
8 | 8 | let ex = map[ey].indexOf("E"); |
9 | | - let current = { x: sx, y: sy, score: 0, dir: "right", path: ["0,0"] }; |
| 9 | + return { map, start: { x: sx, y: sy }, end: { x: ex, y: ey } }; |
| 10 | +} |
| 11 | + |
| 12 | +function getNeighbors(current, map) { |
| 13 | + let neighbors = [ |
| 14 | + { x: current.x - 1, y: current.y, direction: "left" }, |
| 15 | + { x: current.x + 1, y: current.y, direction: "right" }, |
| 16 | + { x: current.x, y: current.y - 1, direction: "up" }, |
| 17 | + { x: current.x, y: current.y + 1, direction: "down" }, |
| 18 | + ] |
| 19 | + .filter(n => n.direction !== opposite[current.direction]) |
| 20 | + .filter(n => map[n.y]?.[n.x] !== "#") |
| 21 | + .map(n => ({ |
| 22 | + ...n, |
| 23 | + path: current.path.concat(`${n.x},${n.y}`), |
| 24 | + score: current.score + (n.direction !== current.direction ? 1001 : 1), |
| 25 | + })); |
| 26 | + return neighbors; |
| 27 | +} |
| 28 | + |
| 29 | +function solve(input, part2 = false) { |
| 30 | + let { map, start, end } = parse(input); |
| 31 | + let current = { ...start, score: 0, direction: "right", path: ["0,0"] }; |
10 | 32 | let queue = [current]; |
11 | 33 | let visited = new Map(); |
12 | | - let min = Infinity; |
13 | | - let paths = []; |
| 34 | + let results = [{ score: Infinity }]; |
14 | 35 | visited.set(`${current.x},${current.y}`, 0); |
15 | 36 | while (queue.length > 0) { |
16 | 37 | current = queue.shift(); |
17 | | - if (current.x === ex && current.y === ey) { |
18 | | - if (current.score < min) paths = [current.path]; |
19 | | - if (current.score === min) paths.push(current.path); |
20 | | - min = Math.min(min, current.score); |
| 38 | + if (current.x === end.x && current.y === end.y) { |
| 39 | + if (current.score < results[0].score) results = [current]; |
| 40 | + if (current.score === results[0].score) results.push(current); |
21 | 41 | continue; |
22 | 42 | } |
23 | | - let neighbors = [ |
24 | | - { x: current.x - 1, y: current.y, dir: "left" }, |
25 | | - { x: current.x + 1, y: current.y, dir: "right" }, |
26 | | - { x: current.x, y: current.y - 1, dir: "up" }, |
27 | | - { x: current.x, y: current.y + 1, dir: "down" }, |
28 | | - ] |
29 | | - .filter(neighbor => neighbor.dir !== opposite[current.dir]) |
30 | | - .filter(neighbor => map[neighbor.y]?.[neighbor.x] !== "#") |
31 | | - .map(neighbor => ({ |
32 | | - ...neighbor, |
33 | | - path: current.path.concat(`${neighbor.x},${neighbor.y}`), |
34 | | - score: current.score + (neighbor.dir !== current.dir ? 1001 : 1), |
35 | | - })); |
36 | | - neighbors.forEach(neighbor => { |
37 | | - if ( |
38 | | - !visited.has(`${neighbor.x},${neighbor.y},${neighbor.dir}`) || |
39 | | - visited.get(`${neighbor.x},${neighbor.y},${neighbor.dir}`) > |
40 | | - neighbor.score |
41 | | - ) { |
42 | | - queue.push(neighbor); |
43 | | - visited.set( |
44 | | - `${neighbor.x},${neighbor.y},${neighbor.dir}`, |
45 | | - neighbor.score + (part2 ? 1 : 0), |
46 | | - ); |
| 43 | + getNeighbors(current, map).forEach(n => { |
| 44 | + const prev = visited.get(`${n.x},${n.y},${n.direction}`) || Infinity; |
| 45 | + // for part two we follow also paths with same score passing the same cell |
| 46 | + // this can easily be optimized by saving the path and going in only once |
| 47 | + // alternatively we could use dfs with memoization to optimize the search |
| 48 | + if (prev > n.score || (part2 && prev === n.score)) { |
| 49 | + queue.push(n); |
| 50 | + visited.set(`${n.x},${n.y},${n.direction}`, n.score); |
47 | 51 | } |
48 | 52 | }); |
49 | 53 | } |
50 | | - return { min, paths }; |
| 54 | + return results; |
51 | 55 | } |
52 | 56 |
|
53 | 57 | export function part1(input) { |
54 | | - return solve(input).min; |
| 58 | + return solve(input)[0].score; |
55 | 59 | } |
56 | 60 |
|
57 | 61 | export function part2(input) { |
58 | | - return solve(input, true).paths.reduce( |
59 | | - (acc, path) => acc.union(new Set(path)), |
60 | | - new Set(), |
61 | | - ).size; |
| 62 | + const results = solve(input, true).map(result => new Set(result.path)); |
| 63 | + return results.reduce((acc, next) => acc.union(next)).size; |
62 | 64 | } |
0 commit comments