From b2eac5d86e6bf3c50b920a414d64ecba95cf9edc Mon Sep 17 00:00:00 2001 From: Anthony McLin Date: Tue, 14 Dec 2021 05:12:34 -0800 Subject: [PATCH 1/2] feat(2021-day-06): count how many laternfish exist after 80 days solves part 1 --- 2021/day-06/fish.js | 42 ++++++++++++++++++++++ 2021/day-06/fish.test.js | 78 ++++++++++++++++++++++++++++++++++++++++ 2021/day-06/index.js | 3 ++ 2021/day-06/input.txt | 1 + 2021/day-06/solution.js | 41 +++++++++++++++++++++ index.js | 2 +- 6 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 2021/day-06/fish.js create mode 100644 2021/day-06/fish.test.js create mode 100644 2021/day-06/index.js create mode 100644 2021/day-06/input.txt create mode 100644 2021/day-06/solution.js diff --git a/2021/day-06/fish.js b/2021/day-06/fish.js new file mode 100644 index 0000000..f3413d1 --- /dev/null +++ b/2021/day-06/fish.js @@ -0,0 +1,42 @@ + +let _fishes = [] +const NewFishAge = 8 // age of newly spawned fish +const FishSpawnAge = 0 // age when the fish spawns +const ResetFishAge = 6 // age of the original fish after spawning + +const ageFish = (age) => { + if (age > NewFishAge) { throw new Error('Fish is too young') } + if (age < FishSpawnAge) { throw new Error('Fish is too old') } + if (age === FishSpawnAge) { return ResetFishAge } + return age - 1 +} + +const spawn = (qty) => { + console.debug(`spawning ${qty} fish`) + const newFishes = [...new Array(qty)].map(() => NewFishAge) + _fishes.push(...newFishes) +} + +const school = { + get state () { + return _fishes + }, + set state (state) { + _fishes = state + }, + + advance: () => { + // Calculate how many will spawn + const toSpawn = _fishes.filter((x) => x === FishSpawnAge).length + // Iterate each fish + _fishes = _fishes.map(ageFish) + // Spawn the new fish + spawn(toSpawn) + } +} + +module.exports = { + school, + ageFish, + spawn +} diff --git a/2021/day-06/fish.test.js b/2021/day-06/fish.test.js new file mode 100644 index 0000000..2d46687 --- /dev/null +++ b/2021/day-06/fish.test.js @@ -0,0 +1,78 @@ +/* eslint-env mocha */ +const { expect } = require('chai') +const { school, ageFish, spawn } = require('./fish') + +describe('--- Day 6: Lanternfish ---', () => { + describe('Part 1', () => { + beforeEach(() => { + // ensure flushed state + school.state = [3, 4, 3, 1, 2] + expect(school.state).to.deep.equal([3, 4, 3, 1, 2]) + }) + describe('spawn()', () => { + it('adds new fish to the end of the list', () => { + spawn(4) + expect(school.state).to.deep.equal([3, 4, 3, 1, 2, 8, 8, 8, 8]) + }) + }) + describe('ageFish()', () => { + it('ages a particular fish', () => { + expect(ageFish(6)).to.equal(5) + expect(ageFish(5)).to.equal(4) + expect(ageFish(4)).to.equal(3) + expect(ageFish(3)).to.equal(2) + expect(ageFish(2)).to.equal(1) + expect(ageFish(1)).to.equal(0) + expect(ageFish(0)).to.equal(6) + expect(ageFish(8)).to.equal(7) + expect(ageFish(7)).to.equal(6) + }) + it('throws an error if the fish is out of range', () => { + expect(() => { ageFish(9) }).to.throw('Fish is too young') + expect(() => { ageFish(-1) }).to.throw('Fish is too old') + }) + }) + describe('advance()', () => { + it('advances one day', () => { + school.state = [3, 4, 3, 1, 2] + school.advance() + expect(school.state).to.deep.equal([2, 3, 2, 0, 1]) + school.advance() + expect(school.state).to.deep.equal([1, 2, 1, 6, 0, 8]) + school.advance() + expect(school.state).to.deep.equal([0, 1, 0, 5, 6, 7, 8]) + school.advance() + expect(school.state).to.deep.equal([6, 0, 6, 4, 5, 6, 7, 8, 8]) + school.advance() + expect(school.state).to.deep.equal([5, 6, 5, 3, 4, 5, 6, 7, 7, 8]) + school.advance() + expect(school.state).to.deep.equal([4, 5, 4, 2, 3, 4, 5, 6, 6, 7]) + school.advance() + expect(school.state).to.deep.equal([3, 4, 3, 1, 2, 3, 4, 5, 5, 6]) + school.advance() + expect(school.state).to.deep.equal([2, 3, 2, 0, 1, 2, 3, 4, 4, 5]) + school.advance() + expect(school.state).to.deep.equal([1, 2, 1, 6, 0, 1, 2, 3, 3, 4, 8]) + school.advance() + expect(school.state).to.deep.equal([0, 1, 0, 5, 6, 0, 1, 2, 2, 3, 7, 8]) + school.advance() + expect(school.state).to.deep.equal([6, 0, 6, 4, 5, 6, 0, 1, 1, 2, 6, 7, 8, 8, 8]) + school.advance() + expect(school.state).to.deep.equal([5, 6, 5, 3, 4, 5, 6, 0, 0, 1, 5, 6, 7, 7, 7, 8, 8]) + school.advance() + expect(school.state).to.deep.equal([4, 5, 4, 2, 3, 4, 5, 6, 6, 0, 4, 5, 6, 6, 6, 7, 7, 8, 8]) + school.advance() + expect(school.state).to.deep.equal([3, 4, 3, 1, 2, 3, 4, 5, 5, 6, 3, 4, 5, 5, 5, 6, 6, 7, 7, 8]) + school.advance() + expect(school.state).to.deep.equal([2, 3, 2, 0, 1, 2, 3, 4, 4, 5, 2, 3, 4, 4, 4, 5, 5, 6, 6, 7]) + school.advance() + expect(school.state).to.deep.equal([1, 2, 1, 6, 0, 1, 2, 3, 3, 4, 1, 2, 3, 3, 3, 4, 4, 5, 5, 6, 8]) + school.advance() + expect(school.state).to.deep.equal([0, 1, 0, 5, 6, 0, 1, 2, 2, 3, 0, 1, 2, 2, 2, 3, 3, 4, 4, 5, 7, 8]) + school.advance() + expect(school.state).to.deep.equal([6, 0, 6, 4, 5, 6, 0, 1, 1, 2, 6, 0, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 8, 8, 8]) + school.advance() + }) + }) + }) +}) diff --git a/2021/day-06/index.js b/2021/day-06/index.js new file mode 100644 index 0000000..af7e035 --- /dev/null +++ b/2021/day-06/index.js @@ -0,0 +1,3 @@ +// eslint-disable-next-line no-unused-vars +const console = require('../helpers') +require('./solution') diff --git a/2021/day-06/input.txt b/2021/day-06/input.txt new file mode 100644 index 0000000..ff0b23a --- /dev/null +++ b/2021/day-06/input.txt @@ -0,0 +1 @@ +1,1,3,5,1,3,2,1,5,3,1,4,4,4,1,1,1,3,1,4,3,1,2,2,2,4,1,1,5,5,4,3,1,1,1,1,1,1,3,4,1,2,2,5,1,3,5,1,3,2,5,2,2,4,1,1,1,4,3,3,3,1,1,1,1,3,1,3,3,4,4,1,1,5,4,2,2,5,4,5,2,5,1,4,2,1,5,5,5,4,3,1,1,4,1,1,3,1,3,4,1,1,2,4,2,1,1,2,3,1,1,1,4,1,3,5,5,5,5,1,2,2,1,3,1,2,5,1,4,4,5,5,4,1,1,3,3,1,5,1,1,4,1,3,3,2,4,2,4,1,5,5,1,2,5,1,5,4,3,1,1,1,5,4,1,1,4,1,2,3,1,3,5,1,1,1,2,4,5,5,5,4,1,4,1,4,1,1,1,1,1,5,2,1,1,1,1,2,3,1,4,5,5,2,4,1,5,1,3,1,4,1,1,1,4,2,3,2,3,1,5,2,1,1,4,2,1,1,5,1,4,1,1,5,5,4,3,5,1,4,3,4,4,5,1,1,1,2,1,1,2,1,1,3,2,4,5,3,5,1,2,2,2,5,1,2,5,3,5,1,1,4,5,2,1,4,1,5,2,1,1,2,5,4,1,3,5,3,1,1,3,1,4,4,2,2,4,3,1,1 diff --git a/2021/day-06/solution.js b/2021/day-06/solution.js new file mode 100644 index 0000000..e8be44d --- /dev/null +++ b/2021/day-06/solution.js @@ -0,0 +1,41 @@ +const fs = require('fs') +const path = require('path') +const filePath = path.join(__dirname, 'input.txt') +const { parseData } = require('../../2018/inputParser') +const { school } = require('./fish') + +fs.readFile(filePath, { encoding: 'utf8' }, (err, initData) => { + if (err) throw err + + initData = parseData(initData.trim()) + + const resetInput = () => { + // Deep copy to ensure we aren't mutating the original data + return JSON.parse(JSON.stringify(initData)) + } + + const part1 = () => { + const data = resetInput() + school.state = data + // Advance the designated time + for (let x = 0; x < 80; x++) { + school.advance() + } + // Count how many fish we have + return school.state.length + } + + const part2 = () => { + const data = resetInput() + console.debug(data) + return 'No answer yet' + } + const answers = [] + answers.push(part1()) + answers.push(part2()) + + answers.forEach((ans, idx) => { + console.info(`-- Part ${idx + 1} --`) + console.info(`Answer: ${ans}`) + }) +}) diff --git a/index.js b/index.js index ed39a00..0d94d20 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -require('./2021/day-05/solution') +require('./2021/day-06/solution') From f46f23a1c5c0b7535cbca75dc6156315155532bb Mon Sep 17 00:00:00 2001 From: Anthony McLin Date: Tue, 14 Dec 2021 06:09:38 -0800 Subject: [PATCH 2/2] feat(2021-day-06): efficient methods for tracking lampfish schools at scale solves part 2 --- 2021/day-06/fish.js | 35 ++++++++++++++++++++- 2021/day-06/fish.test.js | 67 ++++++++++++++++++++++++++++++++++++++-- 2021/day-06/solution.js | 12 +++++-- 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/2021/day-06/fish.js b/2021/day-06/fish.js index f3413d1..f6f5cbc 100644 --- a/2021/day-06/fish.js +++ b/2021/day-06/fish.js @@ -4,6 +4,10 @@ const NewFishAge = 8 // age of newly spawned fish const FishSpawnAge = 0 // age when the fish spawns const ResetFishAge = 6 // age of the original fish after spawning +// allocate an empty big school +const _newBigSchool = () => [...new Array(NewFishAge + 1)].map(() => 0) +let _bigSchool = _newBigSchool() + const ageFish = (age) => { if (age > NewFishAge) { throw new Error('Fish is too young') } if (age < FishSpawnAge) { throw new Error('Fish is too old') } @@ -17,6 +21,11 @@ const spawn = (qty) => { _fishes.push(...newFishes) } +const efficientSpawn = (qty) => { + console.debug(`spawning ${qty} fish`) + _bigSchool[NewFishAge] = qty +} + const school = { get state () { return _fishes @@ -35,8 +44,32 @@ const school = { } } +/** + * The efficient school doesn't track the position of the fish. + * It only cares about the total number of fish of each age + */ +const efficientSchool = { + get state () { + return _bigSchool + }, + set state (state) { + _bigSchool = _newBigSchool() + state.forEach((fish) => { _bigSchool[fish]++ }) + }, + advance: () => { + // Calculate how many will spawn (age 0) and shift the age groups in one quick step + const toSpawn = _bigSchool.shift() + // Iterate old fish back to young since they're spawning + _bigSchool[ResetFishAge] += toSpawn + // Spawn the new fish + efficientSpawn(toSpawn) + } +} + module.exports = { school, + efficientSchool, ageFish, - spawn + spawn, + efficientSpawn } diff --git a/2021/day-06/fish.test.js b/2021/day-06/fish.test.js index 2d46687..bfdb0be 100644 --- a/2021/day-06/fish.test.js +++ b/2021/day-06/fish.test.js @@ -1,6 +1,6 @@ /* eslint-env mocha */ const { expect } = require('chai') -const { school, ageFish, spawn } = require('./fish') +const { school, ageFish, spawn, efficientSchool, efficientSpawn } = require('./fish') describe('--- Day 6: Lanternfish ---', () => { describe('Part 1', () => { @@ -71,7 +71,70 @@ describe('--- Day 6: Lanternfish ---', () => { expect(school.state).to.deep.equal([0, 1, 0, 5, 6, 0, 1, 2, 2, 3, 0, 1, 2, 2, 2, 3, 3, 4, 4, 5, 7, 8]) school.advance() expect(school.state).to.deep.equal([6, 0, 6, 4, 5, 6, 0, 1, 1, 2, 6, 0, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 8, 8, 8]) - school.advance() + }) + }) + }) + describe('Part 2', () => { + beforeEach(() => { + // ensure flushed state + efficientSchool.state = [3, 4, 3, 1, 2] + expect(efficientSchool.state).to.deep.equal([0, 1, 1, 2, 1, 0, 0, 0, 0]) + }) + describe('efficientSpawn()', () => { + it('efficiently adds new fish to the school', () => { + efficientSpawn(4) + expect(efficientSchool.state).to.deep.equal([0, 1, 1, 2, 1, 0, 0, 0, 4]) + }) + }) + describe('efficientAdvance', () => { + it('advances one day following the same pattern without tracking unique position', () => { + const sum = (x, y) => x + y + efficientSchool.state = [3, 4, 3, 1, 2] + + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([2, 3, 2, 0, 1].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([1, 2, 1, 6, 0, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([0, 1, 0, 5, 6, 7, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([6, 0, 6, 4, 5, 6, 7, 8, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([5, 6, 5, 3, 4, 5, 6, 7, 7, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([4, 5, 4, 2, 3, 4, 5, 6, 6, 7].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([3, 4, 3, 1, 2, 3, 4, 5, 5, 6].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([2, 3, 2, 0, 1, 2, 3, 4, 4, 5].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([1, 2, 1, 6, 0, 1, 2, 3, 3, 4, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([0, 1, 0, 5, 6, 0, 1, 2, 2, 3, 7, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([6, 0, 6, 4, 5, 6, 0, 1, 1, 2, 6, 7, 8, 8, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([5, 6, 5, 3, 4, 5, 6, 0, 0, 1, 5, 6, 7, 7, 7, 8, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([4, 5, 4, 2, 3, 4, 5, 6, 6, 0, 4, 5, 6, 6, 6, 7, 7, 8, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([3, 4, 3, 1, 2, 3, 4, 5, 5, 6, 3, 4, 5, 5, 5, 6, 6, 7, 7, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([2, 3, 2, 0, 1, 2, 3, 4, 4, 5, 2, 3, 4, 4, 4, 5, 5, 6, 6, 7].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([1, 2, 1, 6, 0, 1, 2, 3, 3, 4, 1, 2, 3, 3, 3, 4, 4, 5, 5, 6, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([0, 1, 0, 5, 6, 0, 1, 2, 2, 3, 0, 1, 2, 2, 2, 3, 3, 4, 4, 5, 7, 8].length) + efficientSchool.advance() + expect(efficientSchool.state.reduce(sum)).to.equal([6, 0, 6, 4, 5, 6, 0, 1, 1, 2, 6, 0, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 8, 8, 8].length) + }) + it('advances efficiently for a large number of days', () => { + efficientSchool.state = [3, 4, 3, 1, 2] + for (let d = 1; d <= 256; d++) { + efficientSchool.advance() + } + const sum = (x, y) => x + y + efficientSchool.state.reduce(sum) }) }) }) diff --git a/2021/day-06/solution.js b/2021/day-06/solution.js index e8be44d..5fe66a1 100644 --- a/2021/day-06/solution.js +++ b/2021/day-06/solution.js @@ -2,7 +2,7 @@ const fs = require('fs') const path = require('path') const filePath = path.join(__dirname, 'input.txt') const { parseData } = require('../../2018/inputParser') -const { school } = require('./fish') +const { school, efficientSchool } = require('./fish') fs.readFile(filePath, { encoding: 'utf8' }, (err, initData) => { if (err) throw err @@ -27,8 +27,14 @@ fs.readFile(filePath, { encoding: 'utf8' }, (err, initData) => { const part2 = () => { const data = resetInput() - console.debug(data) - return 'No answer yet' + efficientSchool.state = data + // Advance the designated time + for (let x = 0; x < 256; x++) { + efficientSchool.advance() + } + // Count how many fish we have + const sum = (x, y) => x + y + return efficientSchool.state.reduce(sum) } const answers = [] answers.push(part1())