-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #128 from amclin/feat/2020-day-07
Feat/2020 day 07
- Loading branch information
Showing
7 changed files
with
849 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
Oops, something went wrong.