diff --git a/2024/19/2024-19.run.xml b/2024/19/2024-19.run.xml
new file mode 100644
index 0000000..020430f
--- /dev/null
+++ b/2024/19/2024-19.run.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2024/19/index.test.ts b/2024/19/index.test.ts
new file mode 100644
index 0000000..5ff68c4
--- /dev/null
+++ b/2024/19/index.test.ts
@@ -0,0 +1,21 @@
+import { describe, expect, it } from 'bun:test';
+import { solveFirst, solveSecond } from './index.ts';
+
+describe('2024-19', () => {
+ const testInput = `r, wr, b, g, bwu, rb, gb, br
+
+brwrr
+bggr
+gbbr
+rrbgbr
+ubwu
+bwurrg
+brgr
+bbrgwb`;
+ it('first', () => {
+ expect(solveFirst(testInput)).toBe(6);
+ });
+ it('second', () => {
+ expect(solveSecond(testInput)).toBe(16);
+ });
+});
diff --git a/2024/19/index.ts b/2024/19/index.ts
new file mode 100644
index 0000000..874213b
--- /dev/null
+++ b/2024/19/index.ts
@@ -0,0 +1,79 @@
+import { sumArray } from '../../utils/array.ts';
+
+export function solveFirst(input: string): number {
+ const [row, lines] = input.split('\n\n');
+ const towels = row.split(', ').toSorted((a, b) => b.length - a.length);
+ const combinations = lines.split('\n');
+
+ const possibleCombinations = combinations.filter((c) =>
+ isPossible(c, towels)
+ );
+ return possibleCombinations.length;
+}
+
+const lookup = new Map();
+
+export function allPossibilities(
+ combination: string,
+ towels: string[]
+): number {
+ const lookedUp = lookup.get(combination);
+ if (lookedUp !== undefined) {
+ return lookedUp;
+ }
+ if (combination.length <= 0) {
+ lookup.set(combination, 1);
+ return 1;
+ }
+ const prefixes = towels.filter((t) => combination.startsWith(t));
+ if (prefixes.length <= 0) {
+ lookup.set(combination, 0);
+ return 0;
+ }
+
+ const result = sumArray(
+ prefixes.map((p) =>
+ allPossibilities(combination.substring(p.length), towels)
+ )
+ );
+ lookup.set(combination, result);
+
+ return result;
+}
+
+const lookupPossible = new Map();
+
+export function isPossible(combination: string, towels: string[]): boolean {
+ const lookedUp = lookupPossible.get(combination);
+ if (lookedUp !== undefined) {
+ return lookedUp;
+ }
+ if (combination.length <= 0) {
+ lookupPossible.set(combination, true);
+ return true;
+ }
+ const prefixes = towels.filter((t) => combination.startsWith(t));
+ if (prefixes.length <= 0) {
+ lookupPossible.set(combination, false);
+ return false;
+ }
+
+ const result = prefixes.some((p) =>
+ isPossible(combination.substring(p.length), towels)
+ );
+ lookupPossible.set(combination, result);
+
+ return result;
+}
+
+export function solveSecond(input: string): number {
+ const [row, lines] = input.split('\n\n');
+ const towels = row.split(', ').toSorted((a, b) => b.length - a.length);
+ const combinations = lines.split('\n');
+
+ const possibleCombinations = combinations.map((c) =>
+ allPossibilities(c, towels)
+ );
+
+ return sumArray(possibleCombinations);
+}
diff --git a/2024/19/run.ts b/2024/19/run.ts
new file mode 100644
index 0000000..88783cd
--- /dev/null
+++ b/2024/19/run.ts
@@ -0,0 +1,12 @@
+import { join } from 'path';
+import { solveFirst, solveSecond } from './index.ts';
+
+const input = await Bun.file(join(__dirname, 'input.txt')).text();
+
+const firstAnswer = solveFirst(input);
+
+console.log(firstAnswer);
+
+const secondAnswer = solveSecond(input);
+
+console.log(secondAnswer);