diff --git a/2021/day-06/fish.js b/2021/day-06/fish.js new file mode 100644 index 0000000..f6f5cbc --- /dev/null +++ b/2021/day-06/fish.js @@ -0,0 +1,75 @@ + +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 + +// 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') } + 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 efficientSpawn = (qty) => { + console.debug(`spawning ${qty} fish`) + _bigSchool[NewFishAge] = qty +} + +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) + } +} + +/** + * 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, + efficientSpawn +} diff --git a/2021/day-06/fish.test.js b/2021/day-06/fish.test.js new file mode 100644 index 0000000..bfdb0be --- /dev/null +++ b/2021/day-06/fish.test.js @@ -0,0 +1,141 @@ +/* eslint-env mocha */ +const { expect } = require('chai') +const { school, ageFish, spawn, efficientSchool, efficientSpawn } = 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]) + }) + }) + }) + 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/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..5fe66a1 --- /dev/null +++ b/2021/day-06/solution.js @@ -0,0 +1,47 @@ +const fs = require('fs') +const path = require('path') +const filePath = path.join(__dirname, 'input.txt') +const { parseData } = require('../../2018/inputParser') +const { school, efficientSchool } = 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() + 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()) + answers.push(part2()) + + answers.forEach((ans, idx) => { + console.info(`-- Part ${idx + 1} --`) + console.info(`Answer: ${ans}`) + }) +}) diff --git a/README.md b/README.md index a5965c8..279f0ef 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Status -### 2020 +### 2021 ![](https://img.shields.io/badge/day%20📅-14-blue) ![](https://img.shields.io/badge/stars%20⭐-6-yellow) ![](https://img.shields.io/badge/days%20completed-3-red) 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')