Skip to content

Commit

Permalink
Disjoint set
Browse files Browse the repository at this point in the history
  • Loading branch information
hildjj committed Dec 19, 2024
1 parent 95906a8 commit c2d77e4
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 29 deletions.
72 changes: 43 additions & 29 deletions day18.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { AllDirs, Point, PointMap, PointSet, Rect } from './lib/rect.ts';
import {
AllBoxDirs,
AllDirs,
Point,
PointForest,
PointMap,
Rect,
} from './lib/rect.ts';
import { type MainArgs, parseFile } from './lib/utils.ts';
import { BinaryHeap } from '@std/data-structures';

Expand All @@ -7,13 +14,12 @@ type Parsed = [x: number, y: number][];
const SIZE = 71;
const NUM = 1024;

function astar(r: Rect): PointSet {
function astar(r: Rect): number {
const start = new Point(0, 0);
const end = new Point(r.width - 1, r.height - 1);

const gScore = new PointMap([[start, 0]]);
const fScore = new PointMap([[start, 0]]);
const prev = new PointMap<Point>();
const backlog = new BinaryHeap<Point>(
(a, b) => fScore.get(a)! - fScore.get(b)!,
);
Expand All @@ -24,13 +30,7 @@ function astar(r: Rect): PointSet {
const g = gScore.get(p)!;

if (p.equals(end)) {
const ps = new PointSet([start]);
let pp: Point | undefined = p;
while (pp) {
ps.add(pp);
pp = prev.get(pp);
}
return ps;
return g;
}

for (const d of AllDirs) {
Expand All @@ -41,13 +41,12 @@ function astar(r: Rect): PointSet {
gScore.set(next, g + 1);
fScore.set(next, g + next.manhattan(end));
backlog.push(next);
prev.set(next, p);
}
}
}
}

return new PointSet();
return NaN;
}

function part1(inp: Parsed): number {
Expand All @@ -56,26 +55,41 @@ function part1(inp: Parsed): number {
rect.set(...inp[i], '#');
}

return astar(rect).size - 1; // Fencepost
return astar(rect);
}

function part2(inp: Parsed): [number, number] {
const rect = Rect.ofSize(SIZE, SIZE, '.');
// We checked the first NUM in part 1
for (let i = 0; i < NUM; i++) {
rect.set(...inp[i], '#');
}

// Only check if the point hits our existing path.
// Force the first one to match so we get one good path at the beginning.
let path = new PointSet([new Point(...inp[NUM])]);
for (let i = NUM; i < inp.length; i++) {
const p = new Point(...inp[i]);
rect.set(p, '#');
if (path.has(p)) {
path = astar(rect);
if (path.size === 0) {
return inp[i];
// See: https://en.wikipedia.org/wiki/Disjoint-set_data_structure
// If there is a set of dropped points that crosses between top-left and
// bottom-right (including diagonals), then there is no path between those
// points. If there is a a set that goes from left to bottom or top to
// right, it will still allow a path.
const max = SIZE - 1;
const pf = new PointForest();
for (const [x, y] of inp) {
const p = new Point(x, y);
const data = (x === 0x0 ? 0b0001 : 0) | // Left
(x === max ? 0b0010 : 0) | // Right
(y === 0x0 ? 0b0100 : 0) | // Top
(y === max ? 0b1000 : 0); // Bottom
pf.add(p, data);
for (const d of AllBoxDirs) {
const neighbor = p.inBoxDir(d)!;
const un = pf.union(p, neighbor);
switch (un) {
case undefined:
// Neighbor not in set
break;
case 0b0011: // Touches left and right
case 0b0101: // Touches top and left
case 0b0111:
case 0b1010: // Touches bottom and right
case 0b1011:
case 0b1100: // Touches top and bottom
case 0b1101:
case 0b1110:
case 0b1111:
return [x, y];
}
}
}
Expand Down
59 changes: 59 additions & 0 deletions lib/rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -929,3 +929,62 @@ export class PointMap<T> implements Map<Point, T> {
return 'PointSet';
}
}

export interface ForestPoint {
parent: ForestPoint; // TODO(@hildjj): Redo with parent?: ForestPoint
size: number;
data: number;
}

export class PointForest {
#all = new PointMap<ForestPoint>();

#newForestPoint(data: number): ForestPoint {
const fp = {
parent: undefined as (ForestPoint | undefined),
size: 1,
data,
};
fp.parent = fp as ForestPoint;
return fp as ForestPoint;
}

add(p: Point, data: number): void {
let fp = this.#all.get(p);
if (!fp) {
fp = this.#newForestPoint(data);
this.#all.set(p, fp);
} else {
throw new Error(`Dup! ${p}`);
}
}

#find(p: Point): ForestPoint | undefined {
let fp = this.#all.get(p);
if (!fp) {
return undefined;
}
while (fp.parent !== fp) {
// Reset parents as we traverse up.
// This is why x.parent = x at the top, so that grandparent always works.
[fp, fp.parent] = [fp.parent, fp.parent.parent];
fp = fp.parent;
}
return fp;
}

union(a: Point, b: Point): number | undefined {
let afp = this.#find(a);
let bfp = this.#find(b);
if (!afp || !bfp || (afp === bfp)) {
return undefined; // Already in the same set, or one of the points isn't in the set.
}
if (afp.size < bfp.size) {
[afp, bfp] = [bfp, afp];
}
bfp.parent = afp;
afp.size += bfp.size;
afp.data |= bfp.data;
return afp.data;
}
}

0 comments on commit c2d77e4

Please sign in to comment.