From 14a89267fe73029035102444c846e1078933b03a Mon Sep 17 00:00:00 2001 From: Lewis Dale Date: Sun, 3 Dec 2023 11:58:21 +0000 Subject: [PATCH] Day Three, Part One. Structure the codebase a little --- index.ts | 3 ++ package.json | 6 ++- day_one.ts => src/day_one.ts | 0 src/day_three.test.ts | 23 ++++++++ src/day_three.ts | 75 ++++++++++++++++++++++++++ src/day_two.alternative.test.ts | 63 ++++++++++++++++++++++ src/day_two.alternative.ts | 54 +++++++++++++++++++ day_two.test.ts => src/day_two.test.ts | 0 day_two.ts => src/day_two.ts | 4 +- yarn.lock | 10 ++++ 10 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 index.ts rename day_one.ts => src/day_one.ts (100%) create mode 100644 src/day_three.test.ts create mode 100644 src/day_three.ts create mode 100644 src/day_two.alternative.test.ts create mode 100644 src/day_two.alternative.ts rename day_two.test.ts => src/day_two.test.ts (100%) rename day_two.ts => src/day_two.ts (99%) diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..3bd2ad1 --- /dev/null +++ b/index.ts @@ -0,0 +1,3 @@ +import { runDayThree } from "./src/day_three"; + +runDayThree(); \ No newline at end of file diff --git a/package.json b/package.json index 4f0c47f..b2e3f8c 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,20 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest" }, "author": "", "license": "ISC", "devDependencies": { "@types/jest": "^29.5.10", + "@types/lodash": "^4.14.202", "@types/node": "^20.10.1", "jest": "^29.7.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "typescript": "^5.3.2" + }, + "dependencies": { + "lodash": "^4.17.21" } } diff --git a/day_one.ts b/src/day_one.ts similarity index 100% rename from day_one.ts rename to src/day_one.ts diff --git a/src/day_three.test.ts b/src/day_three.test.ts new file mode 100644 index 0000000..ca6f940 --- /dev/null +++ b/src/day_three.test.ts @@ -0,0 +1,23 @@ +import { Engine } from "./day_three"; + +const schematic = `467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598..`; + + +// const schematic = `.....+.58. +// ..592.....`; + +describe('Day Three', () => { + it('should calculate the sum of all part numbers', () => { + const engine = Engine.create(schematic); + expect(engine.sumPartNumbers()).toEqual(4361); + }); +}); \ No newline at end of file diff --git a/src/day_three.ts b/src/day_three.ts new file mode 100644 index 0000000..fcac7c8 --- /dev/null +++ b/src/day_three.ts @@ -0,0 +1,75 @@ +import { takeWhile } from 'lodash'; +import fs from "fs"; +import {calculateMinimumCubePowers, calculatePossibleGames, parseGame} from "./day_two"; + +export class Engine { + constructor(private readonly partNumbers: number[]) { + } + + public static create(input: string): Engine { + const lines = input.split('\n').filter(Boolean); + const partRegex = /\d+/g; + const symbolRegex = /[^a-zA-Z\d\.]/g; + + const parts : number[][] = new Array(lines.length).fill(0).map(() => new Array(lines[0].length)); + const symbols: string[][] = new Array(lines.length).fill(0).map(() => new Array(lines[0].length)); + + lines.forEach((line, lineNumber) => { + const matches = [...line.matchAll(partRegex)]; + matches.map(match => { + if (match && match.index !== undefined) { + const parsedNumber = parseInt(match[0], 10); + for (let i = match.index; i < match.index + match[0].length; i++) { + parts[lineNumber][i] = parsedNumber; + } + } + }); + }); + + lines.forEach((line, lineNumber) => { + const matches = [...line.matchAll(symbolRegex)]; + + matches.map(match => { + if (match && match.index !== undefined) { + symbols[lineNumber][match.index] = match[0]; + } + }); + }); + console.log(symbols.length, parts.length) + + const partsList = symbols.flatMap((row, rowIndex) => + row.map((symbol, index) => { + if (!symbol) return symbol; + + const partIndex = [ + [rowIndex - 1, index - 1], + [rowIndex - 1, index], + [rowIndex - 1, index + 1], + [rowIndex, index - 1], + [rowIndex, index + 1], + [rowIndex + 1, index - 1], + [rowIndex + 1, index], + [rowIndex + 1, index + 1] + ]; + return Array.from(new Set(partIndex.filter(([rowNum, col]) => rowNum >= 0 && rowNum <= symbols.length && index >= 0 && index <= row.length) + .map(([rowNum, column]) => { + return parts[rowNum][column] + }).filter(Boolean))) + .reduce((total, val) => total + val,0); + }) + ) + + return new Engine(partsList as number[]); + } + + public sumPartNumbers(): number { + return this.partNumbers.reduce((total, partNumber) => total + partNumber, 0); + } +} + +export const runDayThree = () => { + const input = fs.readFileSync('./inputs/day_three_input.txt', 'utf8'); + const engine = Engine.create(input) + + console.log(engine.sumPartNumbers()); +} \ No newline at end of file diff --git a/src/day_two.alternative.test.ts b/src/day_two.alternative.test.ts new file mode 100644 index 0000000..a9071a6 --- /dev/null +++ b/src/day_two.alternative.test.ts @@ -0,0 +1,63 @@ +import { Game, calculatePossibleGames, calculateMinimumCubePowers } from "./day_two.alternative"; + +const partOneInput = `Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green +Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue +Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red +Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red +Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green` + +describe('Day Two', () => { + it('should parse a game', () => { + const games = partOneInput.split('\n').map(Game.parse); + expect(games).toEqual([ + new Game(1, [ + { blue: 3, green: 0, red: 4 }, + { blue: 6, red: 1, green: 2 }, + { blue: 0, red: 0, green: 2 } + ] + ), + new Game(2, [ + { blue: 1, green: 2, red: 0 }, + { blue: 4, red: 1, green: 3 }, + { blue: 1, red: 0, green: 1 } + ] + ), + new Game( + 3, + [ + { blue: 6, green: 8, red: 20 }, + { blue: 5, red: 4, green: 13 }, + { blue: 0, red: 1, green: 5 } + ] + ), + new Game( + 4, + [ + { blue: 6, green: 1, red: 3 }, + { blue: 0, red: 6, green: 3 }, + { blue: 15, red: 14, green: 3 } + ] + ), + new Game( + 5, + [ + { blue: 1, green: 3, red: 6 }, + { blue: 2, red: 1, green: 2 }, + ] + ) + ]); + }); + + it('should calculate which games should only have been possible with the number of cubes', () => { + const games = partOneInput.split('\n').map(Game.parse); + expect(calculatePossibleGames(games, { + blue: 14, + red: 12, + green: 13 + })).toEqual(8); + }); + it('should find the minimum cubes in a set and sum the powers of the cubes', () => { + const games = partOneInput.split('\n').map(Game.parse); + expect(calculateMinimumCubePowers(games)).toEqual(2286); + }); +}); \ No newline at end of file diff --git a/src/day_two.alternative.ts b/src/day_two.alternative.ts new file mode 100644 index 0000000..289ea78 --- /dev/null +++ b/src/day_two.alternative.ts @@ -0,0 +1,54 @@ +import { Cubes } from "./day_two"; + +export class Game { + constructor( + public readonly id: number, + private readonly rounds: Cubes[] + ) {} + + public static parse(input: string): Game { + const [gameId, roundsInput] = input.split(':'); + const id = parseInt(gameId.split(' ')[1], 10); + + const rounds = roundsInput.split(';').map(round => { + return round.trim().split(',').map(cube => cube.trim()).reduce((total, cube) => { + const [amount, color] = cube.split(' '); + total[color as keyof Cubes] = parseInt(amount, 10); + return total; + }, {blue: 0, green: 0, red: 0} as Cubes); + }); + + return new Game(id, rounds); + } + + private minimumCubesRequired(): Cubes { + return this.rounds.reduce((total, round) => { + Object.keys(round).forEach(color => { + total[color as keyof Cubes] = Math.max(total[color as keyof Cubes], round[color as keyof Cubes]); + }); + return total; + }, {blue: 0, green: 0, red: 0} as Cubes); + } + + public calculateMinimumCubePowers(): number { + const minimumCubes = this.minimumCubesRequired(); + return minimumCubes.blue * minimumCubes.green * minimumCubes.red; + } + + public isPossible(limits: Cubes): boolean { + return this.rounds.every(round => { + return Object.keys(round).every(color => { + return round[color as keyof Cubes] <= limits[color as keyof Cubes]; + }); + }); + } +} + +export const calculatePossibleGames = (games: Game[], limits: Cubes): number => { + const possibleGames = games.filter(game => game.isPossible(limits)); + return possibleGames.reduce((total, game) => total + game.id, 0); +} + +export const calculateMinimumCubePowers = (games: Game[]): number => { + return games.reduce((total, game) => total + game.calculateMinimumCubePowers(), 0); +} \ No newline at end of file diff --git a/day_two.test.ts b/src/day_two.test.ts similarity index 100% rename from day_two.test.ts rename to src/day_two.test.ts diff --git a/day_two.ts b/src/day_two.ts similarity index 99% rename from day_two.ts rename to src/day_two.ts index ed0af3b..9e93dd0 100644 --- a/day_two.ts +++ b/src/day_two.ts @@ -65,6 +65,4 @@ export const runDayTwo = () => { }; console.log(calculatePossibleGames(games, limits)); console.log(calculateMinimumCubePowers(games)); -} - -runDayTwo(); \ No newline at end of file +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9bbc28b..c206610 100644 --- a/yarn.lock +++ b/yarn.lock @@ -658,6 +658,11 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/lodash@^4.14.202": + version "4.14.202" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" + integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + "@types/node@*": version "20.10.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.2.tgz#32a5e8228357f57714ad28d52229ab04880c2814" @@ -1736,6 +1741,11 @@ lodash.memoize@4.x: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"