Skip to content

Commit 97491c3

Browse files
Merge pull request #128 from amclin/feat/2020-day-07
Feat/2020 day 07
2 parents 2ee7d59 + 4d45cbf commit 97491c3

File tree

7 files changed

+849
-0
lines changed

7 files changed

+849
-0
lines changed

2020/day-07/bagRules.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
const console = require('../helpers')
2+
3+
const parseRule = (rule) => {
4+
const result = {}
5+
// Get the color of the outer bag
6+
const outRemainder = rule.split(' contain ')
7+
result.outer = outRemainder.shift().replace('bags', 'bag')
8+
// Get the color and values of inner bags
9+
if (outRemainder[0] !== 'no other bags.') {
10+
result.inner = outRemainder[0].split(', ').map((str) => {
11+
const inRemainder = str.split(' ')
12+
const count = Number(inRemainder.shift())
13+
const color = inRemainder.join(' ')
14+
.replace('.', '')
15+
.replace('bags', 'bag')
16+
return {
17+
count,
18+
color
19+
}
20+
})
21+
}
22+
23+
return result
24+
}
25+
26+
const findAllowedOuter = (rules, color) => {
27+
const isAllowed = (rule) => {
28+
if (!rule.inner) return false
29+
return (
30+
rule.inner.filter((child) => {
31+
return (
32+
child.color === color
33+
)
34+
}).length > 0
35+
)
36+
}
37+
38+
const allowed = {}
39+
40+
// Loop through the rules, find all colors this bag is allowed within
41+
rules.filter(isAllowed).forEach((rule) => {
42+
allowed[rule.outer] = true
43+
})
44+
45+
// Take the list of allowed colors, and find out which they are allowed within
46+
Object.keys(allowed).forEach((color) => {
47+
const temp = findAllowedOuter(rules, color)
48+
if (Object.keys(temp).length > 0) {
49+
Object.assign(allowed, temp)
50+
}
51+
})
52+
53+
return allowed
54+
}
55+
56+
const countInner = (rules, color, count = 1) => {
57+
// const children = {}
58+
/** checks if rule matches color */
59+
const matchesColor = ({ outer }) => outer === color
60+
/** checks if rule has child bags */
61+
const hasChildren = ({ inner }) => (inner) && inner.length > 0
62+
63+
const getChildrenBags = ({ inner }, multiplier = 1) => {
64+
const res = {}
65+
// Convert into structured list
66+
inner.forEach(({ color, count }) => {
67+
res[color] = count * multiplier
68+
})
69+
return res
70+
}
71+
/** type-safe addition */
72+
const add = (a, b) => {
73+
a = (typeof a === 'number') ? a : 0
74+
b = (typeof b === 'number') ? b : 0
75+
return a + b
76+
}
77+
/** combine two objects using the specified operator method for collsions */
78+
const combineObjects = (a = {}, b = {}, operator) => {
79+
const c = {}
80+
// check for collisions between fields across the objects and run operator() on them
81+
for (const [key, value] of Object.entries(b)) {
82+
c[key] = operator(a[key], value)
83+
}
84+
return Object.assign({}, a, c) // b not needed because covered in collision resolver
85+
}
86+
87+
console.debug('matching', color)
88+
89+
// Loop through the rules to find first level children
90+
return rules
91+
.filter(matchesColor) // find all matches for the color
92+
.filter(hasChildren) // filter for matches that have children
93+
.map(rule => getChildrenBags(rule, count)) // get the counts from the children
94+
.reduce((res, children) => {
95+
// Add everything back together
96+
const childrensChildren = Object.entries(children)
97+
.map(([key, value]) => countInner(rules, key, value))
98+
.reduce((r, c) => combineObjects(r, c, add), {})
99+
100+
res = combineObjects(res, children, add)
101+
res = combineObjects(res, childrensChildren, add)
102+
103+
return res
104+
}, {})
105+
}
106+
107+
module.exports = {
108+
parseRule,
109+
findAllowedOuter,
110+
countInner
111+
}

2020/day-07/bagRules.test.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* eslint-env mocha */
2+
const { expect } = require('chai')
3+
const { parseRule, findAllowedOuter, countInner } = require('./bagRules')
4+
5+
const testData = {
6+
rules: [
7+
'light red bags contain 1 bright white bag, 2 muted yellow bags.',
8+
'dark orange bags contain 3 bright white bags, 4 muted yellow bags.',
9+
'bright white bags contain 1 shiny gold bag.',
10+
'muted yellow bags contain 2 shiny gold bags, 9 faded blue bags.',
11+
'shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags.',
12+
'dark olive bags contain 3 faded blue bags, 4 dotted black bags.',
13+
'vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.',
14+
'faded blue bags contain no other bags.',
15+
'dotted black bags contain no other bags.'
16+
],
17+
part2Rules: [
18+
'shiny gold bags contain 2 dark red bags.',
19+
'dark red bags contain 2 dark orange bags.',
20+
'dark orange bags contain 2 dark yellow bags.',
21+
'dark yellow bags contain 2 dark green bags.',
22+
'dark green bags contain 2 dark blue bags.',
23+
'dark blue bags contain 2 dark violet bags.',
24+
'dark violet bags contain no other bags.'
25+
]
26+
}
27+
28+
describe('--- Day 7: Handy Haversacks ---', () => {
29+
describe('Part 1', () => {
30+
describe('parseRule()', () => {
31+
it('converts a natural language rule into a useable object', () => {
32+
expect(parseRule(testData.rules[0])).to.deep.equal({
33+
outer: 'light red bag',
34+
inner: [
35+
{
36+
count: 1,
37+
color: 'bright white bag'
38+
}, {
39+
count: 2,
40+
color: 'muted yellow bag'
41+
}
42+
]
43+
})
44+
})
45+
it('handles bags that do not accept children', () => {
46+
expect(parseRule(testData.rules[7])).to.deep.equal({
47+
outer: 'faded blue bag'
48+
})
49+
})
50+
})
51+
describe('findAllowedOuter()', () => {
52+
it('list bags the specified bag is allowed to be placed in', () => {
53+
const expectedColors = [
54+
'bright white bag',
55+
'muted yellow bag',
56+
'dark orange bag',
57+
'light red bag'
58+
]
59+
const result = findAllowedOuter(
60+
testData.rules.map(parseRule),
61+
'shiny gold bag'
62+
)
63+
expectedColors.forEach(color => {
64+
expect(result[color]).to.equal(true)
65+
})
66+
expect(Object.keys(result).length).to.equal(expectedColors.length)
67+
})
68+
})
69+
})
70+
describe('Part 2', () => {
71+
describe('countInner()', () => {
72+
it('provides a list of child bags and with quantity of each', () => {
73+
const result1 = Object.values(
74+
countInner(testData.rules.map(parseRule), 'shiny gold bag')
75+
).reduce((a, b) => a + b, 0)
76+
expect(result1).to.equal(32)
77+
78+
const result2 = Object.values(
79+
countInner(testData.part2Rules.map(parseRule), 'shiny gold bag')
80+
).reduce((a, b) => a + b, 0)
81+
expect(result2).to.equal(126)
82+
})
83+
})
84+
})
85+
})

2020/day-07/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// eslint-disable-next-line no-unused-vars
2+
const console = require('../helpers')
3+
require('./solution')

0 commit comments

Comments
 (0)