From 1c7c8bab4dd52e53924ef0bf75834f67b4fdacf0 Mon Sep 17 00:00:00 2001 From: Timothy Quilling Date: Fri, 15 Dec 2023 21:12:02 -0500 Subject: [PATCH] Day 4 --- src/day4/typescript/main.test.ts | 29 +++++++++++++++ src/day4/typescript/main.ts | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/day4/typescript/main.test.ts create mode 100644 src/day4/typescript/main.ts diff --git a/src/day4/typescript/main.test.ts b/src/day4/typescript/main.test.ts new file mode 100644 index 0000000..6be504c --- /dev/null +++ b/src/day4/typescript/main.test.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +import {readFileSync} from 'node:fs'; +import {assertEquals} from 'https://deno.land/std@0.209.0/assert/mod.ts'; +import {assertLess} from 'https://deno.land/std@0.209.0/assert/assert_less.ts'; +import {partA, partB} from './main.ts'; // eslint-disable-line n/file-extension-in-import + +const day = 4; +const answerExample = 13; +const answerA = 22_193; +const answerB = 5_625_994; +const answerExampleB = 30; +const input: string[] = readFileSync(`src/day${day}/input.txt`, 'utf8').split('\n'); +const inputExample: string[] = readFileSync(`src/day${day}/input_example.txt`, 'utf8').split('\n'); + +Deno.test('Example', () => { + assertEquals(partA(inputExample), answerExample); +}); + +Deno.test('Part A', () => { + assertEquals(partA(input), answerA); +}); + +Deno.test('Example B', () => { + assertEquals(partB(inputExample), answerExampleB); +}); + +Deno.test('Part B', () => { + assertEquals(partB(input), answerB); +}); diff --git a/src/day4/typescript/main.ts b/src/day4/typescript/main.ts new file mode 100644 index 0000000..96f8989 --- /dev/null +++ b/src/day4/typescript/main.ts @@ -0,0 +1,62 @@ +export const partA = (input: string[]) => { + let totalPoints = 0; + + for (const card of input) { + const _card = card.slice(card.indexOf(':') + 1).trim(); + const [winningNumbers, yourNumbers] = _card.split('|').map(numbers => numbers.trim().split(/\s+/).map(Number)); + const matches = yourNumbers.filter(number => winningNumbers.includes(number)).length; + totalPoints += matches > 0 ? 2 ** (matches - 1) : 0; + } + + return totalPoints; +}; + +export const partB = (input: string[]) => { + // Let's create a dictionary of matches, so we can easily find it on repeated cards. + type Card = { + cardNumber: number; + winningNumbers: number[]; + yourNumbers: number[]; + matches: number; + additionalCards?: number[]; + card: string; + }; + const cards: Card[] = []; + for (const card of input) { + const cardNumber = input.indexOf(card) + 1; + const _card = card.slice(card.indexOf(':') + 1).trim(); + const [winningNumbers, yourNumbers] = _card.split('|').map(numbers => numbers.trim().split(/\s+/).map(Number)); + const matches = yourNumbers.filter(number => winningNumbers.includes(number)).length; + const additionalCards = matches > 0 ? Array.from({length: matches}, (_, i) => cardNumber + i + 1) : undefined; + cards.push({cardNumber, winningNumbers, yourNumbers, matches, additionalCards, card}); + } + + let totalCards = 0; + + type CardBinder = { + card: Card; + count: number; + }; + const cardBinders: Record = {}; + for (const card of cards) { + cardBinders[card.cardNumber] = {card, count: 1}; + } + + for (const binder of Object.values(cardBinders)) { + if (binder.card.matches === 0) { + continue; + } else if (binder.card.matches > 0) { + for (const cardNumber of binder.card.additionalCards!) { + cardBinders[cardNumber].count += Number(binder.count); + } + } + } + + for (const binder of Object.values(cardBinders)) { + totalCards += binder.count; + } + + console.log(`Total cards: ${totalCards}`); + + return totalCards; +}; -- 2.51.2