Skip to content

Commit

Permalink
Add PointSet
Browse files Browse the repository at this point in the history
  • Loading branch information
hildjj committed Dec 8, 2024
1 parent eb120b4 commit 158a4ba
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 32 deletions.
14 changes: 7 additions & 7 deletions day5.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Point, PointSet } from './lib/rect.ts';
import { type MainArgs, parseFile } from './lib/utils.ts';

type Parsed = [[number, number][], number[][]];
Expand All @@ -16,19 +17,18 @@ function part2(inp: number[][][]): number {

export default async function main(args: MainArgs): Promise<[number, number]> {
const inp = await parseFile<Parsed>(args);
const order = new Set<string>();
// These are number tuples, not points, but... shrug.
const order = new PointSet();
for (const [x, y] of inp[0]) {
order.add(`${x},${y}`);
order.add(new Point(x, y));
}
const beforeAndAfter = inp[1].map((pages) => {
const sorted = [...pages].sort((a, b) => {
if (order.has(`${a},${b}`)) {
if (order.has(new Point(a, b))) {
return -1;
}
if (order.has(`${b},${a}`)) {
return 1;
}
return 0;
// There are no pairs that aren't in one direction or the other.
return 1;
});
return [pages, sorted];
});
Expand Down
35 changes: 16 additions & 19 deletions day8.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Point, Rect } from './lib/rect.ts';
import { Point, PointSet, Rect } from './lib/rect.ts';
import { Sequence } from './lib/sequence.ts';
import { type MainArgs, parseFile } from './lib/utils.ts';

type Parsed = string[][];

class Field extends Rect {
antennae = new Map<string, Point[]>();
nodes: Point[] = [];
nodes = new PointSet();

constructor(inp: Parsed, self = false) {
super(inp);
Expand All @@ -22,22 +22,21 @@ class Field extends Rect {
const p = new Point(x, y);
m.push(p);
if (self) {
this.nodes.push(p);
this.nodes.add(p);
}
});
}

push(p: Point): boolean {
if (this.check(p)) {
this.nodes.push(p);
this.nodes.add(p);
return true;
}
return false;
}

count(): number {
const locs = new Set(this.nodes.map((n) => n.toString()));
return locs.size;
get count(): number {
return this.nodes.size;
}
}

Expand All @@ -46,34 +45,32 @@ function part1(inp: Parsed): number {

for (const [_k, v] of r.antennae) {
for (const [a, b] of new Sequence(v).combinations(2)) {
const dx = a.x - b.x;
const dy = a.y - b.y;
const [dx, dy] = a.delta(b);
r.push(a.xlate(dx, dy));
r.push(b.xlate(-dx, -dy));
}
}

return r.count();
return r.count;
}

function part2(inp: Parsed): number {
const r = new Field(inp, true);

for (const [_k, v] of r.antennae) {
for (const [a, b] of new Sequence(v).combinations(2)) {
const dx = a.x - b.x;
const dy = a.y - b.y;
let p = a.xlate(dx, dy);
while (r.push(p)) {
const [dx, dy] = a.delta(b);
let p = a;
do {
p = p.xlate(dx, dy);
}
p = b.xlate(-dx, -dy);
while (r.push(p)) {
} while (r.push(p));
p = b;
do {
p = p.xlate(-dx, -dy);
}
} while (r.push(p));
}
}
return r.count();
return r.count;
}

export default async function main(args: MainArgs): Promise<[number, number]> {
Expand Down
1 change: 1 addition & 0 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"tasks": {
"check": "deno fmt --check && deno lint && deno check **/*.ts **/*.js",
"test": "rm -rf coverage && deno test -A --coverage --parallel --shuffle && deno coverage coverage --html --exclude='test/**/*' --exclude=day.ts && deno coverage coverage --lcov --output=coverage/lcov.info",
"test:lib": "rm -rf coverage && deno test -A --coverage --parallel --shuffle lib/test/*.test.ts && deno coverage coverage --html --exclude='test/**/*' --exclude=day.ts && deno coverage coverage --lcov --output=coverage/lcov.info",
"ci": "deno test -A --coverage && deno coverage coverage --lcov --output=coverage/lcov.info --exclude='test/**/*' --exclude=day.ts",
"update": "deno run -A jsr:@molt/cli --dry-run",
"docs": "deno doc --html --name=AdventOfCode2024 lib/*.ts",
Expand Down
220 changes: 215 additions & 5 deletions lib/rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export class Point implements PointLike {
}

static sort(a: Point, b: Point): number {
return (a.x - b.x) || (a.y - b.y);
const [dx, dy] = a.delta(b);
return dx || dy;
}

xlate(d: PointLike): Point;
Expand Down Expand Up @@ -107,11 +108,16 @@ export class Point implements PointLike {
}

dist(p: PointLike): number {
return Math.sqrt(Math.abs(this.x - p.x) ** 2 + Math.abs(this.y - p.y) ** 2);
const [dx, dy] = this.delta(p);
return Math.sqrt((dx ** 2) + (dy ** 2));
}

manhattan(p: PointLike): number {
return Math.abs(this.x - p.x) + Math.abs(this.y - p.y);
return this.delta(p).reduce((t, d) => t + Math.abs(d), 0);
}

delta(p: PointLike): [dx: number, dy: number] {
return [this.x - p.x, this.y - p.y];
}

equals(p: PointLike): boolean {
Expand Down Expand Up @@ -584,10 +590,214 @@ export class InfiniteRect<T> extends Rect<T> {
}

slice(min: Point, max: Point): Rect<T> {
const [dx, dy] = max.delta(min);
return InfiniteRect.ofSize<T>(
max.x - min.x + 1,
max.y - min.y + 1,
dx + 1,
dy + 1,
(x: number, y: number): T => this.get(x, y),
);
}
}

/**
* Massive overkill of a class so that I don't have to convert points to and
* from strings or numbers by hand as frequently just to store them in a set.
*/
export class PointSet {
#set: Set<number>;
#bits: number;

constructor(iterable?: Iterable<Point> | null, bits = 24) {
this.#set = new Set();
this.#bits = bits;
if (iterable) {
for (const i of iterable) {
this.#set.add(i.toNumber(this.#bits));
}
}
}

/**
* Appends a new element with a specified value to the end of the Set.
*/
add(value: Point): this {
this.#set.add(value.toNumber(this.#bits));
return this;
}

/**
* Clear all entries from the set.
*/
clear(): void {
this.#set.clear();
}

/**
* Removes a specified value from the Set.
*
* @returns Returns true if an element in the Set existed and has been
* removed, or false if the element does not exist.
*/
delete(value: Point): boolean {
return this.#set.delete(value.toNumber(this.#bits));
}

/**
* Executes a provided function once per each value in the Set object, in
* insertion order.
*/
forEach(
callbackfn: (value: Point, key: Point, set: PointSet) => void,
thisArg?: unknown,
): void {
thisArg ??= this;
this.#set.forEach((value, _key) => {
const p = Point.fromNumber(value);
callbackfn.call(thisArg, p, p, this);
});
}

/**
* @returns a boolean indicating whether an element with the specified value exists in the Set or not.
*/
has(value: Point): boolean {
return this.#set.has(value.toNumber(this.#bits));
}

/**
* @returns the number of (unique) elements in Set.
*/
get size(): number {
return this.#set.size;
}

/** Iterates over values in the set. */
*[Symbol.iterator](): SetIterator<Point> {
for (const n of this.#set) {
yield Point.fromNumber(n, this.#bits);
}
}

/**
* Returns an iterable of [v,v] pairs for every value `v` in the set.
*/
*entries(): SetIterator<[Point, Point]> {
for (const n of this.#set) {
const p = Point.fromNumber(n, this.#bits);
yield [p, p];
}
}

/**
* Despite its name, returns an iterable of the values in the set.
*/
*keys(): SetIterator<Point> {
for (const n of this.#set) {
yield Point.fromNumber(n, this.#bits);
}
}

/**
* Returns an iterable of values in the set.
*/
*values(): SetIterator<Point> {
for (const n of this.#set) {
yield Point.fromNumber(n, this.#bits);
}
}

#checkBits(other: PointSet): void {
if (other.#bits !== this.#bits) {
throw new Error(`Incompatible bits: ${this.#bits} != ${other.#bits}`);
}
}

/**
* @returns a new Set containing all the elements in this Set and also all
* the elements in the argument.
*/
union(other: PointSet): PointSet {
this.#checkBits(other);
const res = new PointSet(null, this.#bits);
res.#set = this.#set.union(other.#set);
return res;
}

/**
* @returns a new Set containing all the elements which are both in this Set
* and in the argument.
*/
intersection(other: PointSet): PointSet {
this.#checkBits(other);
const res = new PointSet(null, this.#bits);
res.#set = this.#set.intersection(other.#set);
return res;
}

/**
* @returns a new Set containing all the elements in this Set which are not
* also in the argument.
*/
difference(other: PointSet): PointSet {
this.#checkBits(other);
const res = new PointSet(null, this.#bits);
res.#set = this.#set.difference(other.#set);
return res;
}

/**
* @returns a new Set containing all the elements which are in either this
* Set or in the argument, but not in both.
*/
symmetricDifference(other: PointSet): PointSet {
this.#checkBits(other);
const res = new PointSet(null, this.#bits);
res.#set = this.#set.symmetricDifference(other.#set);
return res;
}

/**
* @returns a boolean indicating whether all the elements in this Set are
* also in the argument.
*/
isSubsetOf(other: PointSet): boolean {
this.#checkBits(other);
return this.#set.isSubsetOf(other.#set);
}

/**
* @returns a boolean indicating whether all the elements in the argument
* are also in this Set.
*/
isSupersetOf(other: PointSet): boolean {
this.#checkBits(other);
return this.#set.isSupersetOf(other.#set);
}

/**
* @returns a boolean indicating whether this Set has no elements in common
* with the argument.
*/
isDisjointFrom(other: PointSet): boolean {
this.#checkBits(other);
return this.#set.isDisjointFrom(other.#set);
}

[Symbol.for('Deno.customInspect')](): string {
let ret = `PointSet(${this.size}) { `;
let first = true;
for (const p of this) {
if (first) {
first = false;
} else {
ret += ', ';
}
ret += `[${p.toString()}]`;
}
if (!first) {
ret += ' ';
}
ret += '}';
return ret;
}
}
Loading

0 comments on commit 158a4ba

Please sign in to comment.