Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions 2020/day-07/bagRules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const console = require('../helpers')

const parseRule = (rule) => {
const result = {}
// Get the color of the outer bag
const outRemainder = rule.split(' contain ')
result.outer = outRemainder.shift().replace('bags', 'bag')
// Get the color and values of inner bags
if (outRemainder[0] !== 'no other bags.') {
result.inner = outRemainder[0].split(', ').map((str) => {
const inRemainder = str.split(' ')
const count = Number(inRemainder.shift())
const color = inRemainder.join(' ')
.replace('.', '')
.replace('bags', 'bag')
return {
count,
color
}
})
}

return result
}

const findAllowedOuter = (rules, color) => {
const isAllowed = (rule) => {
if (!rule.inner) return false
return (
rule.inner.filter((child) => {
return (
child.color === color
)
}).length > 0
)
}

const allowed = {}

// Loop through the rules, find all colors this bag is allowed within
rules.filter(isAllowed).forEach((rule) => {
allowed[rule.outer] = true
})

// Take the list of allowed colors, and find out which they are allowed within
Object.keys(allowed).forEach((color) => {
const temp = findAllowedOuter(rules, color)
if (Object.keys(temp).length > 0) {
Object.assign(allowed, temp)
}
})

return allowed
}

const countInner = (rules, color, count = 1) => {
// const children = {}
/** checks if rule matches color */
const matchesColor = ({ outer }) => outer === color
/** checks if rule has child bags */
const hasChildren = ({ inner }) => (inner) && inner.length > 0

const getChildrenBags = ({ inner }, multiplier = 1) => {
const res = {}
// Convert into structured list
inner.forEach(({ color, count }) => {
res[color] = count * multiplier
})
return res
}
/** type-safe addition */
const add = (a, b) => {
a = (typeof a === 'number') ? a : 0
b = (typeof b === 'number') ? b : 0
return a + b
}
/** combine two objects using the specified operator method for collsions */
const combineObjects = (a = {}, b = {}, operator) => {
const c = {}
// check for collisions between fields across the objects and run operator() on them
for (const [key, value] of Object.entries(b)) {
c[key] = operator(a[key], value)
}
return Object.assign({}, a, c) // b not needed because covered in collision resolver
}

console.debug('matching', color)

// Loop through the rules to find first level children
return rules
.filter(matchesColor) // find all matches for the color
.filter(hasChildren) // filter for matches that have children
.map(rule => getChildrenBags(rule, count)) // get the counts from the children
.reduce((res, children) => {
// Add everything back together
const childrensChildren = Object.entries(children)
.map(([key, value]) => countInner(rules, key, value))
.reduce((r, c) => combineObjects(r, c, add), {})

res = combineObjects(res, children, add)
res = combineObjects(res, childrensChildren, add)

return res
}, {})
}

module.exports = {
parseRule,
findAllowedOuter,
countInner
}
85 changes: 85 additions & 0 deletions 2020/day-07/bagRules.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* eslint-env mocha */
const { expect } = require('chai')
const { parseRule, findAllowedOuter, countInner } = require('./bagRules')

const testData = {
rules: [
'light red bags contain 1 bright white bag, 2 muted yellow bags.',
'dark orange bags contain 3 bright white bags, 4 muted yellow bags.',
'bright white bags contain 1 shiny gold bag.',
'muted yellow bags contain 2 shiny gold bags, 9 faded blue bags.',
'shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags.',
'dark olive bags contain 3 faded blue bags, 4 dotted black bags.',
'vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.',
'faded blue bags contain no other bags.',
'dotted black bags contain no other bags.'
],
part2Rules: [
'shiny gold bags contain 2 dark red bags.',
'dark red bags contain 2 dark orange bags.',
'dark orange bags contain 2 dark yellow bags.',
'dark yellow bags contain 2 dark green bags.',
'dark green bags contain 2 dark blue bags.',
'dark blue bags contain 2 dark violet bags.',
'dark violet bags contain no other bags.'
]
}

describe('--- Day 7: Handy Haversacks ---', () => {
describe('Part 1', () => {
describe('parseRule()', () => {
it('converts a natural language rule into a useable object', () => {
expect(parseRule(testData.rules[0])).to.deep.equal({
outer: 'light red bag',
inner: [
{
count: 1,
color: 'bright white bag'
}, {
count: 2,
color: 'muted yellow bag'
}
]
})
})
it('handles bags that do not accept children', () => {
expect(parseRule(testData.rules[7])).to.deep.equal({
outer: 'faded blue bag'
})
})
})
describe('findAllowedOuter()', () => {
it('list bags the specified bag is allowed to be placed in', () => {
const expectedColors = [
'bright white bag',
'muted yellow bag',
'dark orange bag',
'light red bag'
]
const result = findAllowedOuter(
testData.rules.map(parseRule),
'shiny gold bag'
)
expectedColors.forEach(color => {
expect(result[color]).to.equal(true)
})
expect(Object.keys(result).length).to.equal(expectedColors.length)
})
})
})
describe('Part 2', () => {
describe('countInner()', () => {
it('provides a list of child bags and with quantity of each', () => {
const result1 = Object.values(
countInner(testData.rules.map(parseRule), 'shiny gold bag')
).reduce((a, b) => a + b, 0)
expect(result1).to.equal(32)

const result2 = Object.values(
countInner(testData.part2Rules.map(parseRule), 'shiny gold bag')
).reduce((a, b) => a + b, 0)
expect(result2).to.equal(126)
})
})
})
})
3 changes: 3 additions & 0 deletions 2020/day-07/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// eslint-disable-next-line no-unused-vars
const console = require('../helpers')
require('./solution')
Loading