Skip to content

Commit

Permalink
Merge pull request #128 from amclin/feat/2020-day-07
Browse files Browse the repository at this point in the history
Feat/2020 day 07
  • Loading branch information
amclin authored Dec 9, 2020
2 parents 2ee7d59 + 4d45cbf commit 97491c3
Show file tree
Hide file tree
Showing 7 changed files with 849 additions and 0 deletions.
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

0 comments on commit 97491c3

Please sign in to comment.