Skip to content

Commit 9cdc695

Browse files
Merge pull request #7 from amclin/feature/2018-day-14
Feature/2018 day 14 part 2
2 parents c6dd678 + d70721a commit 9cdc695

File tree

5 files changed

+168
-107
lines changed

5 files changed

+168
-107
lines changed

2018/day-14/recipes.js

Lines changed: 88 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
1+
class Elf {
2+
constructor (location) {
3+
this.location = location
4+
}
5+
6+
move (distance) {
7+
for (let x = 0; x < distance; x++) {
8+
this.location = this.location.next
9+
}
10+
}
11+
}
12+
113
/**
214
* Circular linked list of recipes
15+
* @param {Array} recipes list of initial recipe values
316
*/
417
class Recipes {
5-
constructor (recipe) {
18+
constructor (recipes) {
619
this.head = null
720
this.tail = null
821
this.length = 0
9-
this.addFirst(recipe)
22+
this.elves = []
23+
this._addFirst(recipes[0])
24+
for (let x = 1; x < recipes.length; x++) {
25+
this.addRecipe(recipes[x])
26+
}
27+
this.addElf(this.tail)
28+
this.addElf(this.head)
1029
}
1130

12-
addFirst (recipe) {
31+
/**
32+
* @private
33+
*/
34+
_addFirst (recipe) {
1335
const newRecipe = { value: recipe }
1436
newRecipe.next = newRecipe
1537
newRecipe.prev = newRecipe
@@ -19,6 +41,14 @@ class Recipes {
1941
return this
2042
}
2143

44+
/**
45+
* Adds an elf (location marker) to the linked list for easier iterative tracking
46+
* @param {*} location Item on the linked list that the elf is positioned at
47+
*/
48+
addElf (location) {
49+
this.elves.push(new Elf(location))
50+
}
51+
2252
/**
2353
* Adds a recipe to the linked list
2454
* @param {Number} recipe value
@@ -57,54 +87,88 @@ const totalDigitsInArray = (arr) => {
5787

5888
/**
5989
* Loops the elves through the recipes list the specified number of times
60-
* @param {Array} elves list of elves
6190
* @param {LinkedList} recipes list of recipes
62-
* @param {Numbe} repeat count of desired iterations
91+
* @param {Number} repeat count of desired iterations
6392
*/
64-
const loopRecipesForElves = (elves, recipes, repeat) => {
93+
const loopRecipesForElves = (recipes, repeat) => {
94+
let result = ''
6595
for (let x = 1; x <= repeat; x++) {
66-
const score = totalDigitsInArray(elves.map((elf) => elf.value))
96+
const score = totalDigitsInArray(recipes.elves.map((elf) => elf.location.value))
97+
result += score.toString()
6798
recipes.scoreRecipes(score)
68-
elves.forEach((elf, idx) => {
69-
const distance = elf.value + 1
70-
for (let x = 0; x < distance; x++) {
71-
elf = elf.next
72-
}
73-
elves[idx] = elf
99+
recipes.elves.forEach((elf, idx) => {
100+
const distance = elf.location.value + 1
101+
elf.move(distance)
74102
})
75103
}
104+
return result
76105
}
77106

78107
/**
79108
* Determines the next X recipes after the elves have generated Y recipes
80109
*/
81-
const calculateXAfterY = (x, y, recipes, elves) => {
110+
const calculateXAfterY = (x, y, recipes) => {
82111
let iterator = recipes.head
83-
while (recipes.length <= y) {
84-
loopRecipesForElves(elves, recipes, 1)
112+
let counter = recipes.length
113+
while (counter < y) {
114+
loopRecipesForElves(recipes, 1)
115+
counter = recipes.length
85116
}
86117

87-
if (recipes.length === y + 1) {
88-
iterator = recipes.head
89-
} else {
90-
// In case multidigit recipe results created more than Y
91-
iterator = recipes.head.prev
92-
}
118+
// In case multidigit recipe results created more than Y
119+
iterator = (recipes.length > y) ? recipes.head.prev : recipes.head
93120

121+
// Add enough recipes to cover X
94122
while (recipes.length < x + y) {
95-
loopRecipesForElves(elves, recipes, 1)
123+
loopRecipesForElves(recipes, x + y - recipes.length)
96124
}
97125

98126
let result = ''
99127
while (result.length < x) {
100-
result += iterator.value.toString()
101128
iterator = iterator.next
129+
result += iterator.value.toString()
102130
}
103131
return result
104132
}
105133

134+
/**
135+
* Counts how many recipes are to the left of the specified pattern
136+
* @param {String} pattern to search for
137+
* @param {LinkedList} recipes recipe list
138+
* @param {Number} bufferSize bucket size to search. Tuning bucket size can improve speed by adjusting memory usage.
139+
*/
140+
const findPattern = (pattern, recipes, bufferSize) => {
141+
bufferSize = bufferSize || 101
142+
let matched = false
143+
let position = recipes.length
144+
let overlapBuffer = ''
145+
146+
while (matched !== true) {
147+
// console.log(`Checking for ${pattern} in segement starting at ${position}`)
148+
let haystack = loopRecipesForElves(recipes, bufferSize)
149+
150+
let offset = haystack.indexOf(pattern)
151+
152+
position = (offset >= 0) ? position + offset : recipes.length
153+
if (offset > -1) {
154+
// console.log(`Found ${pattern} at ${haystack.substr(0, offset + pattern.length)}`)
155+
matched = true
156+
}
157+
// Use another small buffer to check the string that overlaps the split between buffer segements
158+
overlapBuffer = overlapBuffer.substr(overlapBuffer.length - 1 - pattern.length, pattern.length)
159+
overlapBuffer += haystack.substr(0, pattern.length)
160+
if (overlapBuffer.indexOf(pattern) > -1) {
161+
position = position - pattern.length + overlapBuffer.indexOf(pattern)
162+
matched = true
163+
}
164+
}
165+
166+
return position
167+
}
168+
106169
module.exports = {
107170
calculateXAfterY,
171+
findPattern,
108172
loopRecipesForElves,
109173
Recipes,
110174
totalDigitsInArray

2018/day-14/recipes.test.js

Lines changed: 59 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,32 @@
22
const expect = require('chai').expect
33
const {
44
calculateXAfterY,
5+
findPattern,
56
loopRecipesForElves,
67
Recipes,
78
totalDigitsInArray
89
} = require('./recipes')
910

1011
describe('--- Day 14: Chocolate Charts ---', () => {
12+
let recipes
1113
describe('Part 1:', () => {
14+
beforeEach(() => {
15+
recipes = new Recipes([3, 7])
16+
})
1217
describe('new Recipes()', () => {
1318
it('builds a linked list', () => {
14-
const recipes = new Recipes(0)
19+
expect(recipes.head.value).to.equal(7)
1520
for (let x = 1; x <= 5; x++) {
1621
recipes.addRecipe(x)
1722
}
18-
expect(recipes.length).to.equal(6)
23+
expect(recipes.length).to.equal(7)
1924
expect(recipes.head.value).to.equal(5)
20-
expect(recipes.tail.value).to.equal(0)
25+
expect(recipes.tail.value).to.equal(3)
2126
expect(recipes.tail.prev).to.equal(recipes.head) // circular linked list for prev
2227
expect(recipes.head.next).to.equal(recipes.tail) // circular linked list for next
2328
})
2429
describe('scoreRecipes()', () => {
2530
it('adds new recipes based on the provided score', () => {
26-
const recipes = new Recipes(0)
2731
for (let x = 1; x <= 5; x++) {
2832
recipes.addRecipe(x)
2933
}
@@ -46,95 +50,80 @@ describe('--- Day 14: Chocolate Charts ---', () => {
4650
describe('loopRecipeForEleves()', () => {
4751
it('loops through the recipe object for the specified elves the specified number of times', () => {
4852
const expected = '37101012451589167792' // list of recipe values in the last iteration of the example
49-
const elves = [3, 7]
50-
const recipes = new Recipes(elves[0])
51-
let actual = ''
5253

53-
elves.forEach((elf, idx) => {
54-
if (idx === 0) {
55-
elves[0] = recipes.head
56-
} else {
57-
elves[idx] = recipes.addRecipe(elf)
58-
}
59-
})
54+
loopRecipesForElves(recipes, 15)
55+
let actual = recipes.tail.value.toString()
56+
let iterator = recipes.tail.next
57+
while (iterator !== recipes.tail) {
58+
actual += iterator.value.toString()
59+
iterator = iterator.next
60+
}
6061

61-
loopRecipesForElves(elves, recipes, 15)
62+
expect(expected).to.equal(actual)
63+
})
64+
it('handles when the score is multidigit', () => {
65+
const expected = '3710101245158916'
6266

67+
loopRecipesForElves(recipes, 10)
68+
// next should be multidigit
69+
loopRecipesForElves(recipes, 1)
70+
let actual = recipes.tail.value.toString()
6371
let iterator = recipes.tail.next
64-
actual += recipes.tail.value.toString()
6572
while (iterator !== recipes.tail) {
6673
actual += iterator.value.toString()
6774
iterator = iterator.next
6875
}
69-
76+
expect(recipes.length).to.equal(expected.length)
7077
expect(expected).to.equal(actual)
7178
})
7279
})
73-
describe('calculateXAfterY(x, y, recipe, elves)', () => {
80+
describe('calculateXAfterY(x, y, recipe)', () => {
7481
it('predicts the next X results after the elves have executed Y', () => {
75-
const elves = [3, 7]
76-
const recipes = new Recipes(elves[0])
77-
let actual = ''
78-
79-
elves.forEach((elf, idx) => {
80-
if (idx === 0) {
81-
elves[0] = recipes.head
82-
} else {
83-
elves[idx] = recipes.addRecipe(elf)
84-
}
85-
})
86-
87-
actual = calculateXAfterY(10, 9, recipes, elves)
82+
let actual = calculateXAfterY(10, 9, recipes)
8883
expect(actual).to.equal('5158916779')
8984
})
9085
it('predicts the next X results after the elves have executed Y', () => {
91-
const elves = [3, 7]
92-
const recipes = new Recipes(elves[0])
93-
let actual = ''
94-
95-
elves.forEach((elf, idx) => {
96-
if (idx === 0) {
97-
elves[0] = recipes.head
98-
} else {
99-
elves[idx] = recipes.addRecipe(elf)
100-
}
101-
})
102-
103-
actual = calculateXAfterY(10, 5, recipes, elves)
86+
const actual = calculateXAfterY(10, 5, recipes)
10487
expect(actual).to.equal('0124515891')
10588
})
10689
it('predicts the next X results after the elves have executed Y', () => {
107-
const elves = [3, 7]
108-
const recipes = new Recipes(elves[0])
109-
let actual = ''
110-
111-
elves.forEach((elf, idx) => {
112-
if (idx === 0) {
113-
elves[0] = recipes.head
114-
} else {
115-
elves[idx] = recipes.addRecipe(elf)
116-
}
117-
})
118-
119-
actual = calculateXAfterY(10, 18, recipes, elves)
90+
const actual = calculateXAfterY(10, 18, recipes)
12091
expect(actual).to.equal('9251071085')
12192
})
12293
it('predicts the next X results after the elves have executed Y', () => {
123-
const elves = [3, 7]
124-
const recipes = new Recipes(elves[0])
125-
let actual = ''
126-
127-
elves.forEach((elf, idx) => {
128-
if (idx === 0) {
129-
elves[0] = recipes.head
130-
} else {
131-
elves[idx] = recipes.addRecipe(elf)
132-
}
133-
})
134-
135-
actual = calculateXAfterY(10, 2018, recipes, elves)
94+
const actual = calculateXAfterY(10, 2018, recipes)
13695
expect(actual).to.equal('5941429882')
13796
})
97+
it('positions results correctly if X triggers 2 recipes being added', () => {
98+
let actual = calculateXAfterY(3, 15, recipes)
99+
expect(actual).to.equal('677')
100+
})
101+
})
102+
describe('findPattern()', () => {
103+
it('counts the number of recipes to the left of the specified pattern', () => {
104+
const actual = findPattern('51589', recipes)
105+
expect(actual).to.equal(9)
106+
})
107+
it('counts the number of recipes to the left of the specified pattern', () => {
108+
const actual = findPattern('01245', recipes)
109+
expect(actual).to.equal(5)
110+
})
111+
it('counts the number of recipes to the left of the specified pattern', () => {
112+
const actual = findPattern('92510', recipes)
113+
expect(actual).to.equal(18)
114+
})
115+
it('counts the number of recipes to the left of the specified pattern', () => {
116+
const actual = findPattern('59414', recipes)
117+
expect(actual).to.equal(2018)
118+
})
119+
it('accepts small search buffer sizes', () => {
120+
const actual = findPattern('59414', recipes, 20)
121+
expect(actual).to.equal(2018)
122+
})
123+
it('accepts large search buffer sizes', () => {
124+
const actual = findPattern('59414', recipes, 50000)
125+
expect(actual).to.equal(2018)
126+
})
138127
})
139128
})
140129
})

2018/day-14/solution.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
const {
22
calculateXAfterY,
3+
findPattern,
4+
loopRecipesForElves,
35
Recipes
46
} = require('./recipes')
57

68
const input = 540561
79

8-
const elves = [3, 7]
9-
const recipes = new Recipes(elves[0])
10+
let recipes = new Recipes([3, 7])
1011

11-
elves.forEach((elf, idx) => {
12-
if (idx === 0) {
13-
elves[0] = recipes.head
14-
} else {
15-
elves[idx] = recipes.addRecipe(elf)
16-
}
17-
})
18-
19-
const answer = calculateXAfterY(10, input, recipes, elves)
20-
const answer2 = ''
12+
const answer = calculateXAfterY(10, input, recipes)
2113

2214
console.log(`-- Part 1 --`)
2315
console.log(`Answer: ${answer}`)
16+
17+
recipes = new Recipes([3, 7])
18+
while (recipes.length < 3000) {
19+
loopRecipesForElves(recipes, 1)
20+
}
21+
22+
recipes = new Recipes([3, 7])
23+
const bufferSize = 10000
24+
const answer2 = findPattern(input.toString(), recipes, bufferSize)
2425
console.log(`-- Part 2 --`)
2526
console.log(`Answer: ${answer2}`)

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
[![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)
22
[![Build Status](https://travis-ci.org/amclin/advent-of-code.svg?branch=master)](https://travis-ci.org/amclin/advent-of-code)
33
[![codecov](https://codecov.io/gh/amclin/advent-of-code/branch/master/graph/badge.svg)](https://codecov.io/gh/amclin/advent-of-code)
4+
5+
### Special Instructions
6+
7+
#### Day 14
8+
Day 14 is fast but needs more memory to complete. Run node with 4GB of heap space:
9+
`node --max_old_space_size=4096 index.js`

0 commit comments

Comments
 (0)