Skip to content

Commit

Permalink
Merge pull request #7 from amclin/feature/2018-day-14
Browse files Browse the repository at this point in the history
Feature/2018 day 14 part 2
  • Loading branch information
amclin authored Dec 23, 2018
2 parents c6dd678 + d70721a commit 9cdc695
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 107 deletions.
112 changes: 88 additions & 24 deletions 2018/day-14/recipes.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
class Elf {
constructor (location) {
this.location = location
}

move (distance) {
for (let x = 0; x < distance; x++) {
this.location = this.location.next
}
}
}

/**
* Circular linked list of recipes
* @param {Array} recipes list of initial recipe values
*/
class Recipes {
constructor (recipe) {
constructor (recipes) {
this.head = null
this.tail = null
this.length = 0
this.addFirst(recipe)
this.elves = []
this._addFirst(recipes[0])
for (let x = 1; x < recipes.length; x++) {
this.addRecipe(recipes[x])
}
this.addElf(this.tail)
this.addElf(this.head)
}

addFirst (recipe) {
/**
* @private
*/
_addFirst (recipe) {
const newRecipe = { value: recipe }
newRecipe.next = newRecipe
newRecipe.prev = newRecipe
Expand All @@ -19,6 +41,14 @@ class Recipes {
return this
}

/**
* Adds an elf (location marker) to the linked list for easier iterative tracking
* @param {*} location Item on the linked list that the elf is positioned at
*/
addElf (location) {
this.elves.push(new Elf(location))
}

/**
* Adds a recipe to the linked list
* @param {Number} recipe value
Expand Down Expand Up @@ -57,54 +87,88 @@ const totalDigitsInArray = (arr) => {

/**
* Loops the elves through the recipes list the specified number of times
* @param {Array} elves list of elves
* @param {LinkedList} recipes list of recipes
* @param {Numbe} repeat count of desired iterations
* @param {Number} repeat count of desired iterations
*/
const loopRecipesForElves = (elves, recipes, repeat) => {
const loopRecipesForElves = (recipes, repeat) => {
let result = ''
for (let x = 1; x <= repeat; x++) {
const score = totalDigitsInArray(elves.map((elf) => elf.value))
const score = totalDigitsInArray(recipes.elves.map((elf) => elf.location.value))
result += score.toString()
recipes.scoreRecipes(score)
elves.forEach((elf, idx) => {
const distance = elf.value + 1
for (let x = 0; x < distance; x++) {
elf = elf.next
}
elves[idx] = elf
recipes.elves.forEach((elf, idx) => {
const distance = elf.location.value + 1
elf.move(distance)
})
}
return result
}

/**
* Determines the next X recipes after the elves have generated Y recipes
*/
const calculateXAfterY = (x, y, recipes, elves) => {
const calculateXAfterY = (x, y, recipes) => {
let iterator = recipes.head
while (recipes.length <= y) {
loopRecipesForElves(elves, recipes, 1)
let counter = recipes.length
while (counter < y) {
loopRecipesForElves(recipes, 1)
counter = recipes.length
}

if (recipes.length === y + 1) {
iterator = recipes.head
} else {
// In case multidigit recipe results created more than Y
iterator = recipes.head.prev
}
// In case multidigit recipe results created more than Y
iterator = (recipes.length > y) ? recipes.head.prev : recipes.head

// Add enough recipes to cover X
while (recipes.length < x + y) {
loopRecipesForElves(elves, recipes, 1)
loopRecipesForElves(recipes, x + y - recipes.length)
}

let result = ''
while (result.length < x) {
result += iterator.value.toString()
iterator = iterator.next
result += iterator.value.toString()
}
return result
}

/**
* Counts how many recipes are to the left of the specified pattern
* @param {String} pattern to search for
* @param {LinkedList} recipes recipe list
* @param {Number} bufferSize bucket size to search. Tuning bucket size can improve speed by adjusting memory usage.
*/
const findPattern = (pattern, recipes, bufferSize) => {
bufferSize = bufferSize || 101
let matched = false
let position = recipes.length
let overlapBuffer = ''

while (matched !== true) {
// console.log(`Checking for ${pattern} in segement starting at ${position}`)
let haystack = loopRecipesForElves(recipes, bufferSize)

let offset = haystack.indexOf(pattern)

position = (offset >= 0) ? position + offset : recipes.length
if (offset > -1) {
// console.log(`Found ${pattern} at ${haystack.substr(0, offset + pattern.length)}`)
matched = true
}
// Use another small buffer to check the string that overlaps the split between buffer segements
overlapBuffer = overlapBuffer.substr(overlapBuffer.length - 1 - pattern.length, pattern.length)
overlapBuffer += haystack.substr(0, pattern.length)
if (overlapBuffer.indexOf(pattern) > -1) {
position = position - pattern.length + overlapBuffer.indexOf(pattern)
matched = true
}
}

return position
}

module.exports = {
calculateXAfterY,
findPattern,
loopRecipesForElves,
Recipes,
totalDigitsInArray
Expand Down
129 changes: 59 additions & 70 deletions 2018/day-14/recipes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,32 @@
const expect = require('chai').expect
const {
calculateXAfterY,
findPattern,
loopRecipesForElves,
Recipes,
totalDigitsInArray
} = require('./recipes')

describe('--- Day 14: Chocolate Charts ---', () => {
let recipes
describe('Part 1:', () => {
beforeEach(() => {
recipes = new Recipes([3, 7])
})
describe('new Recipes()', () => {
it('builds a linked list', () => {
const recipes = new Recipes(0)
expect(recipes.head.value).to.equal(7)
for (let x = 1; x <= 5; x++) {
recipes.addRecipe(x)
}
expect(recipes.length).to.equal(6)
expect(recipes.length).to.equal(7)
expect(recipes.head.value).to.equal(5)
expect(recipes.tail.value).to.equal(0)
expect(recipes.tail.value).to.equal(3)
expect(recipes.tail.prev).to.equal(recipes.head) // circular linked list for prev
expect(recipes.head.next).to.equal(recipes.tail) // circular linked list for next
})
describe('scoreRecipes()', () => {
it('adds new recipes based on the provided score', () => {
const recipes = new Recipes(0)
for (let x = 1; x <= 5; x++) {
recipes.addRecipe(x)
}
Expand All @@ -46,95 +50,80 @@ describe('--- Day 14: Chocolate Charts ---', () => {
describe('loopRecipeForEleves()', () => {
it('loops through the recipe object for the specified elves the specified number of times', () => {
const expected = '37101012451589167792' // list of recipe values in the last iteration of the example
const elves = [3, 7]
const recipes = new Recipes(elves[0])
let actual = ''

elves.forEach((elf, idx) => {
if (idx === 0) {
elves[0] = recipes.head
} else {
elves[idx] = recipes.addRecipe(elf)
}
})
loopRecipesForElves(recipes, 15)
let actual = recipes.tail.value.toString()
let iterator = recipes.tail.next
while (iterator !== recipes.tail) {
actual += iterator.value.toString()
iterator = iterator.next
}

loopRecipesForElves(elves, recipes, 15)
expect(expected).to.equal(actual)
})
it('handles when the score is multidigit', () => {
const expected = '3710101245158916'

loopRecipesForElves(recipes, 10)
// next should be multidigit
loopRecipesForElves(recipes, 1)
let actual = recipes.tail.value.toString()
let iterator = recipes.tail.next
actual += recipes.tail.value.toString()
while (iterator !== recipes.tail) {
actual += iterator.value.toString()
iterator = iterator.next
}

expect(recipes.length).to.equal(expected.length)
expect(expected).to.equal(actual)
})
})
describe('calculateXAfterY(x, y, recipe, elves)', () => {
describe('calculateXAfterY(x, y, recipe)', () => {
it('predicts the next X results after the elves have executed Y', () => {
const elves = [3, 7]
const recipes = new Recipes(elves[0])
let actual = ''

elves.forEach((elf, idx) => {
if (idx === 0) {
elves[0] = recipes.head
} else {
elves[idx] = recipes.addRecipe(elf)
}
})

actual = calculateXAfterY(10, 9, recipes, elves)
let actual = calculateXAfterY(10, 9, recipes)
expect(actual).to.equal('5158916779')
})
it('predicts the next X results after the elves have executed Y', () => {
const elves = [3, 7]
const recipes = new Recipes(elves[0])
let actual = ''

elves.forEach((elf, idx) => {
if (idx === 0) {
elves[0] = recipes.head
} else {
elves[idx] = recipes.addRecipe(elf)
}
})

actual = calculateXAfterY(10, 5, recipes, elves)
const actual = calculateXAfterY(10, 5, recipes)
expect(actual).to.equal('0124515891')
})
it('predicts the next X results after the elves have executed Y', () => {
const elves = [3, 7]
const recipes = new Recipes(elves[0])
let actual = ''

elves.forEach((elf, idx) => {
if (idx === 0) {
elves[0] = recipes.head
} else {
elves[idx] = recipes.addRecipe(elf)
}
})

actual = calculateXAfterY(10, 18, recipes, elves)
const actual = calculateXAfterY(10, 18, recipes)
expect(actual).to.equal('9251071085')
})
it('predicts the next X results after the elves have executed Y', () => {
const elves = [3, 7]
const recipes = new Recipes(elves[0])
let actual = ''

elves.forEach((elf, idx) => {
if (idx === 0) {
elves[0] = recipes.head
} else {
elves[idx] = recipes.addRecipe(elf)
}
})

actual = calculateXAfterY(10, 2018, recipes, elves)
const actual = calculateXAfterY(10, 2018, recipes)
expect(actual).to.equal('5941429882')
})
it('positions results correctly if X triggers 2 recipes being added', () => {
let actual = calculateXAfterY(3, 15, recipes)
expect(actual).to.equal('677')
})
})
describe('findPattern()', () => {
it('counts the number of recipes to the left of the specified pattern', () => {
const actual = findPattern('51589', recipes)
expect(actual).to.equal(9)
})
it('counts the number of recipes to the left of the specified pattern', () => {
const actual = findPattern('01245', recipes)
expect(actual).to.equal(5)
})
it('counts the number of recipes to the left of the specified pattern', () => {
const actual = findPattern('92510', recipes)
expect(actual).to.equal(18)
})
it('counts the number of recipes to the left of the specified pattern', () => {
const actual = findPattern('59414', recipes)
expect(actual).to.equal(2018)
})
it('accepts small search buffer sizes', () => {
const actual = findPattern('59414', recipes, 20)
expect(actual).to.equal(2018)
})
it('accepts large search buffer sizes', () => {
const actual = findPattern('59414', recipes, 50000)
expect(actual).to.equal(2018)
})
})
})
})
25 changes: 13 additions & 12 deletions 2018/day-14/solution.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
const {
calculateXAfterY,
findPattern,
loopRecipesForElves,
Recipes
} = require('./recipes')

const input = 540561

const elves = [3, 7]
const recipes = new Recipes(elves[0])
let recipes = new Recipes([3, 7])

elves.forEach((elf, idx) => {
if (idx === 0) {
elves[0] = recipes.head
} else {
elves[idx] = recipes.addRecipe(elf)
}
})

const answer = calculateXAfterY(10, input, recipes, elves)
const answer2 = ''
const answer = calculateXAfterY(10, input, recipes)

console.log(`-- Part 1 --`)
console.log(`Answer: ${answer}`)

recipes = new Recipes([3, 7])
while (recipes.length < 3000) {
loopRecipesForElves(recipes, 1)
}

recipes = new Recipes([3, 7])
const bufferSize = 10000
const answer2 = findPattern(input.toString(), recipes, bufferSize)
console.log(`-- Part 2 --`)
console.log(`Answer: ${answer2}`)
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![Build Status](https://travis-ci.org/amclin/advent-of-code.svg?branch=master)](https://travis-ci.org/amclin/advent-of-code)
[![codecov](https://codecov.io/gh/amclin/advent-of-code/branch/master/graph/badge.svg)](https://codecov.io/gh/amclin/advent-of-code)

### Special Instructions

#### Day 14
Day 14 is fast but needs more memory to complete. Run node with 4GB of heap space:
`node --max_old_space_size=4096 index.js`
Loading

0 comments on commit 9cdc695

Please sign in to comment.