From dbc65901467f28374e816e218bd29d3325b9530a Mon Sep 17 00:00:00 2001 From: Joe Hildebrand Date: Sun, 22 Dec 2024 00:32:04 -0700 Subject: [PATCH 1/2] Day 22 --- day22.peggy | 4 ++++ day22.ts | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ inputs | 2 +- lib/ring.ts | 40 ++++++++++++++++++++++++++++++++++++ test/day22.js | 1 + 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 day22.peggy create mode 100644 day22.ts create mode 100644 lib/ring.ts create mode 100644 test/day22.js diff --git a/day22.peggy b/day22.peggy new file mode 100644 index 0000000..87899a6 --- /dev/null +++ b/day22.peggy @@ -0,0 +1,4 @@ +lines = (@num "\n")* + +num = n:$[0-9]+ { return BigInt(n) } +_ = [ \t]+ diff --git a/day22.ts b/day22.ts new file mode 100644 index 0000000..4dc0af4 --- /dev/null +++ b/day22.ts @@ -0,0 +1,56 @@ +import { type MainArgs, parseFile } from './lib/utils.ts'; +import { Counter } from './lib/counter.ts'; +import { Ring } from './lib/ring.ts'; + +type Parsed = bigint[]; + +const PRUNE = 16777216n; +function nextSecret(n: bigint): bigint { + n = ((n * 64n) ^ n) % PRUNE; + n = ((n / 32n) ^ n) % PRUNE; + n = ((n * 2048n) ^ n) % PRUNE; + return n; +} + +function part1(inp: Parsed): bigint { + let tot = 0n; + for (let n of inp) { + for (let i = 0; i < 2000; i++) { + n = nextSecret(n); + } + tot += n; + } + return tot; +} + +function part2(inp: Parsed): number { + const patterns = new Counter(); + for (let n of inp) { + const c = new Ring(4); + const seen = new Set(); + + for (let i = 0; i < 2000; i++) { + const nextN = nextSecret(n); + const cost = n % 10n; + const nextCost = nextN % 10n; + c.push(nextCost - cost); + + if (c.full) { + const key = c.get().join(','); + if (!seen.has(key)) { + patterns.sum(Number(nextCost), key); + seen.add(key); + } + } + n = nextN; + } + } + + const [_maxKey, max] = patterns.max()!; + return max; +} + +export default async function main(args: MainArgs): Promise<[bigint, number]> { + const inp = await parseFile(args); + return [part1(inp), part2(inp)]; +} diff --git a/inputs b/inputs index 74b6d99..5546f39 160000 --- a/inputs +++ b/inputs @@ -1 +1 @@ -Subproject commit 74b6d9986f6fb07545c2e8eac57b2302df99abc4 +Subproject commit 5546f39ec7b2665ef816585515a5ac8c8e7c3585 diff --git a/lib/ring.ts b/lib/ring.ts new file mode 100644 index 0000000..ac20956 --- /dev/null +++ b/lib/ring.ts @@ -0,0 +1,40 @@ +/** + * Ring Buffer. + */ +export class Ring { + #length: number; + #buf: T[]; + #count = 0; + + constructor(length: number) { + this.#length = length; + this.#buf = Array.from({ length }); + } + + push(val: T): void { + this.#buf[this.#count++ % this.#length] = val; + } + + get(): T[] { + if (this.#count < this.#length) { + return this.#buf.slice(0, this.#count); + } + const pos = this.#count % this.#length; + if (pos === 0) { + return this.#buf.slice(0); + } + return [...this.#buf.slice(pos), ...this.#buf.slice(0, pos)]; + } + + get count(): number { + return this.#count; + } + + get size(): number { + return (this.#count > this.#length) ? this.#length : this.#count; + } + + get full(): boolean { + return this.#count >= this.#length; + } +} diff --git a/test/day22.js b/test/day22.js new file mode 100644 index 0000000..a79a159 --- /dev/null +++ b/test/day22.js @@ -0,0 +1 @@ +export default [20071921341n, 2242]; From 830b70a707bfb916be3d64720bfd2c6af02f60f5 Mon Sep 17 00:00:00 2001 From: Joe Hildebrand Date: Sun, 22 Dec 2024 00:49:54 -0700 Subject: [PATCH 2/2] cleanup --- day22.ts | 7 ++++--- lib/ring.ts | 47 +++++++++++++++++++++++++++++++++++++++++++ lib/test/ring.test.ts | 32 +++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 lib/test/ring.test.ts diff --git a/day22.ts b/day22.ts index 4dc0af4..8f37304 100644 --- a/day22.ts +++ b/day22.ts @@ -4,6 +4,7 @@ import { Ring } from './lib/ring.ts'; type Parsed = bigint[]; +// Has to be bigints, since we go over 2^32. const PRUNE = 16777216n; function nextSecret(n: bigint): bigint { n = ((n * 64n) ^ n) % PRUNE; @@ -26,12 +27,12 @@ function part1(inp: Parsed): bigint { function part2(inp: Parsed): number { const patterns = new Counter(); for (let n of inp) { - const c = new Ring(4); + const c = new Ring(4); const seen = new Set(); + let cost = n % 10n; for (let i = 0; i < 2000; i++) { const nextN = nextSecret(n); - const cost = n % 10n; const nextCost = nextN % 10n; c.push(nextCost - cost); @@ -42,7 +43,7 @@ function part2(inp: Parsed): number { seen.add(key); } } - n = nextN; + [n, cost] = [nextN, nextCost]; } } diff --git a/lib/ring.ts b/lib/ring.ts index ac20956..9ee2685 100644 --- a/lib/ring.ts +++ b/lib/ring.ts @@ -1,20 +1,37 @@ /** * Ring Buffer. + * TODO (@hildjj): allow destructive reads. */ export class Ring { #length: number; #buf: T[]; #count = 0; + /** + * Creates an initially-empty buffer. + * + * @param length Maximum number of things to store. + */ constructor(length: number) { this.#length = length; this.#buf = Array.from({ length }); } + /** + * Add a value to the end, bumping off the oldest entry if the buffer + * is full. + * + * @param val + */ push(val: T): void { this.#buf[this.#count++ % this.#length] = val; } + /** + * Get the current buffer contents. + * + * @returns A copy of the current contents, in logical order. + */ get(): T[] { if (this.#count < this.#length) { return this.#buf.slice(0, this.#count); @@ -26,15 +43,45 @@ export class Ring { return [...this.#buf.slice(pos), ...this.#buf.slice(0, pos)]; } + /** + * How many times has this buffer been added to? + * + * @readonly + * @type {number} + */ get count(): number { return this.#count; } + /** + * Current size. Maxes out at length; + * + * @readonly + * @type {number} + */ get size(): number { return (this.#count > this.#length) ? this.#length : this.#count; } + /** + * Is the buffer full already? + * + * @readonly + * @type {boolean} + */ get full(): boolean { return this.#count >= this.#length; } + + /** + * Get the original length of the buffer. + * TODO (@hildjj): Allow setting the length to a new value, potentially + * losing data from the beginning of the buffer. + * + * @readonly + * @type {number} + */ + get length(): number { + return this.#length; + } } diff --git a/lib/test/ring.test.ts b/lib/test/ring.test.ts new file mode 100644 index 0000000..3114abb --- /dev/null +++ b/lib/test/ring.test.ts @@ -0,0 +1,32 @@ +import { assertEquals } from '@std/assert/equals'; +import { Ring } from '../ring.ts'; + +Deno.test('Ring', async (t) => { + await t.step('create', () => { + const r = new Ring(4); + assertEquals(r.get(), []); + assertEquals(r.size, 0); + assertEquals(r.count, 0); + assertEquals(r.full, false); + + r.push(1); + assertEquals(r.get(), [1]); + assertEquals(r.size, 1); + assertEquals(r.count, 1); + assertEquals(r.full, false); + + r.push(2); + r.push(3); + r.push(4); + assertEquals(r.get(), [1, 2, 3, 4]); + assertEquals(r.size, 4); + assertEquals(r.count, 4); + assertEquals(r.full, true); + + r.push(5); + assertEquals(r.get(), [2, 3, 4, 5]); + assertEquals(r.size, 4); + assertEquals(r.count, 5); + assertEquals(r.full, true); + }); +});