What I learned from attempting Advent of Code 2017, 2016 & 2015 all at one go.
Yong Jun
24 Jan 2018
by Eric Wastl
Advent of Code a series of small programming puzzles for a variety of skill levels.
They are self-contained and are just as appropriate for an expert who wants to stay sharp as they are for a beginner who is just learning to code.
Each puzzle calls upon different skills and has two parts that build on a theme.
function* generator () {
// do something
yield 'someValue'
// do some more thing
yield 'anotherValue'
}
const iterator = generator()
console.log(iterator.next())
// {value: 'someValue', done: false}
console.log(iterator.next())
// {value: 'anotherValue', done: false}
console.log(iterator.next())
// {value: undefined, done: true}
Right x 1
Up x 1
Left x 2
Down x 2
Right x 3
Up x 3
Left x 4
Down x 4
function * traverse () {
let step = 1
let x = 0
let y = 0
yield [x, y]
while (true) {
for (let right = step; right > 0; right--) {
yield [++x, y]
}
for (let up = step; up > 0; up--) {
yield [x, ++y]
}
step++
for (let left = step; left > 0; left--) {
yield [--x, y]
}
for (let down = step; down > 0; down--) {
yield [x, --y]
}
step++
}
}
function manhattanDistance (target) {
let coordinates
const location = traverse()
for (let i = 0; i < target; i++) {
coordinates = location.next().value
}
return Math.abs(coordinates[0]) + Math.abs(coordinates[1])
}
Rather than create an object that maintain internal state by variables assignment (explicit)
function * reindeer (speed, fly, rest) {
let distance = 0
while (true) {
for (let i = 0; i < fly; i++) {
distance += speed
yield distance
}
for (let i = 0; i < rest; i++) {
yield distance
}
}
}
Task:
Find ABBA
Solution:
function containsAbba (str) {
const match = str.match(/([a-z])([a-z])\2\1/)
if (!match) return false
if (match[1] !== match[2]) return true
return containsAbba(str.slice(match.index + 1))
}
// no proper tail call
function lookNsay (str) {
const match = str.match(/(.)(\1*)/)
if (match[0].length === str.length) return match[0].length + match[1]
return match[0].length + match[1] + lookNsay(str.slice(match[0].length))
}
// with proper tail call
function lookNsay (str, prefix = '') {
const match = str.match(/(.)(\1*)/)
if (match[0].length === str.length) return prefix + match[0].length + match[1]
return lookNsay(str.slice(match[0].length), prefix + match[0].length + match[1])
}
Task:
Given containers of various sizes (eg. 20, 15, 10, 5, and 5).
Find how many different combinations total up to a given capacity.
For example if required total is 25, four ways are possible:
Solution:
Enumerate all possible assignment combination
Eg. 000, 001, 010, 011, 100, 101, 110, 111
Task:
Given distances between each location pair, find the
shortest route where every location is visited exactly once.
Solution:
Enumerate all possible permutation
Eg. ABC, ACB, BAC, BCA, CAB, CBA
Task:
Find the ideal seating arrangement around a round
dining table
Solution:
Fix a start point and enumerate all possible
permutation that start and end with that point. Eg.
Task:
With exactly 100 teaspoons of ingredients. Find
the ideal recipe
Solution:
Enumeration all possible split combinations. Eg.
0/0/100 | |||
0/1/99 | 1/0/99 | ||
0/2/98 | 1/2/98 | 2/1/98 | |
0/3/97 | 1/2/97 | 2/1/97 | 3/0/97 |
function getAssignments (items, groups = 2) {
const combinations = []
const nCombinations = Math.pow(groups, items)
const zeroPad = '0'.repeat(items)
for (let i = 0; i < nCombinations; i++) {
const combiString = (zeroPad + i.toString(groups)).slice(-items)
const combination = combiString.split('')
if (groups === 2) combinations.push(combination.map(v => +v))
else combinations.push(combination)
}
return combinations
}
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|
First cut is always at 0
Last cut is always at N
Second cut at 2
Third cut also at 2
Fourth cut at 5
function getSplitCombinations (total, items) {
const combinations = []
function recurse (division, i) {
if (division.length < items - 1) {
while (i <= total) {
recurse(division.concat(i), i)
i++
}
} else {
division = [0, ...division, total]
const combination = []
for (let i = 1; i < division.length; i++) {
combination.push(division[i] - division[i - 1])
}
combinations.push(combination)
}
}
recurse([], 0)
return combinations
}
Task:
Split cargo (28 pieces) into 3 groups of equal weight.
Pick combination with least items in the first group.
Analysis:
Total possible combinations is !!!
Takes too long to enumerate every possible combination.
Need a way to exit early once a match is found.
const iterable = {
[Symbol.iterator]: function* () {
let n = 0
while (true) {
yield n++
}
}
}
for (let n of iterable) {
if (n >= 10) break
}
// lazy version of assignment enumerator
function getAssignments (items, groups = 2) {
const nCombinations = Math.pow(groups, items)
const zeroPad = '0'.repeat(items)
return {
[Symbol.iterator]: function* () {
for (let i = 0; i < nCombinations; i++) {
const combiString = (zeroPad + i.toString(groups)).slice(-items)
const combination = combiString.split('')
if (groups === 2) yield combination.map(v => +v)
else yield combination
}
}
}
}
// wrong use
const iterable = getAssignment(n)
Array.from(iterable)
[...iterable]
// correct use
const iterable = getAssignment(n)
for (let item of iterable) {
if (/* condition met */) return item
}
function bfs (root) {
const visited = {}
const unvisited = []
unvisited.push([root, 0])
while (unvisited.length > 0) {
const [next, steps] = unvisited.shift()
if (next in visited) continue
visited[next] = 1
if (found) return steps
next.children
.forEach(child => {
unvisited.push([child, steps + 1]
})
}
}
function dfs (root) {
const visited = {}
const unvisited = []
unvisited.push([root, 0])
while (unvisited.length > 0) {
const [next, steps] = unvisited.pop()
if (next in visited) continue
visited[next] = 1
if (found) return steps
next.children
.sort(sortFunc)
.reverse()
.forEach(child => {
unvisited.push([child, steps + 1]
})
}
}