diff --git a/2021/day-08/display.test.js b/2021/day-08/display.test.js index fdf3439..535324e 100644 --- a/2021/day-08/display.test.js +++ b/2021/day-08/display.test.js @@ -18,15 +18,16 @@ gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce describe('--- Day 8: Seven Segment Search ---', () => { describe('Part 1', () => { describe('descrambleSignal()', () => { - const testData = testSingle.split('|')[0].trim() - const { segmentCodes, charCodes } = descrambleSignal(testData) - it('takes scambled string of 10 codes and identifies the letters matching each seven-digit-display segment', () => { + const testData = testSingle.split('|')[0].trim() + const { segmentCodes } = descrambleSignal(testData) expect(segmentCodes.length).to.equal(7) expect(segmentCodes.filter((code) => !['a', 'b', 'c', 'd', 'e', 'f', 'g'].includes(code)).length).to.equal(0) }) it('produces a list of character codes for each number that can be displayed', () => { + const testData = testSingle.split('|')[0].trim() + const { charCodes } = descrambleSignal(testData) // There should be exactly 10 numbers expect(charCodes.length).to.equal(10) // lengths of each code is predictable as each number has a specific count of segments @@ -35,10 +36,9 @@ describe('--- Day 8: Seven Segment Search ---', () => { }) }) describe('decodeSignal()', () => { - const testData = testMultiple[0].split('|').map((a) => a.trim()) - const { charCodes } = descrambleSignal(testData[0]) - it('decodes a display pattern using the provided map of display codes', () => { + const testData = testMultiple[0].split('|').map((a) => a.trim()) + const { charCodes } = descrambleSignal(testData[0]) const result = decodeSignal(charCodes, testData[1]) expect(result[0]).to.equal(8) expect(result[3]).to.equal(4) diff --git a/2021/day-10/index.js b/2021/day-10/index.js new file mode 100644 index 0000000..af7e035 --- /dev/null +++ b/2021/day-10/index.js @@ -0,0 +1,3 @@ +// eslint-disable-next-line no-unused-vars +const console = require('../helpers') +require('./solution') diff --git a/2021/day-10/input.txt b/2021/day-10/input.txt new file mode 100644 index 0000000..f8430ee --- /dev/null +++ b/2021/day-10/input.txt @@ -0,0 +1,94 @@ +[[<[<{[<{{<[{(()[])[[]()]}[((){})<[]{}>]}<<{[]{}}<(){}>><([]<>)>>>[[[<<>>[<><>]][<<>>(<><>)]][{ +((<[(<<(<{<((<()<>>{()()}){<<>><()[]>})([{<>{}}(()[])]{(<><>>})>(<((()())<[][]>)>([(<>{})({}())][< +<<<(<<[[((({<([]<>)[[]]>{<{}>{(){}}}}{[<[][]><<>[]>]({<><>}{()<>})})[<<({}())(()<>)>[([]{})<[]()>]>({[[] +<(<<<[((([{({((){})<<>{}>}[<(){}>[<>()]])[[<{}[]>({}{}]](<<>[]><[]<>>)]}](<[({[][]}<()<>>)<[ +{<<<{[({{(<<(<<>[]><[]()>){<[][]>{<>{}}}>((({}())([]<>))[<{}{}>({}())])>(<<<()[]>><<<>{}>(()< +{<{<<[[(<<[{<<{}[]>>([<>()]{{}{}})}]<(<{<>()}{{}()}>{({}[])<<>()>})>>>){[{[{<[[]{}][<><>]>}{(<{}( +(((([(({((<[<(<>{})[{}{}]>](<[()<>]{<><>}>)><<[<[]{}>((){})]<[()[]]>>{[{<>()}((){})]({()[]}(()[]))}>))}( +<<[(({({(<{{((<>[])[[]<>])([<>]{()()})}<<{<>[]}><<{}()>>>}>)(<(<{<()[]><(){}>}{{(){}}[{}<>]}>{ +(([[{<[<<{[<{<()[]>{<>[]}}{<{}{}>{[]<>}}>[{<{}{}>(<>[])}]]{<<({}[])>(<[]<>>[{}])>({{<>()}(< +(<{{(<<{[([<<{()[]}>{({}<>){<>[]}]>((<[]{}><{}{}>))]([{(()<>)(<>[])}]<<((){})<<>{}>>[(()[]) +{{[[<<{<{{{([{()[]}[{}()]]{(<>{}){<><>}}){<[[][]][()[]]>}}<{{<[][]>[{}[]]>(<<>{}><{}{}>)}>}}<<{[{([]( +{[<({{{[<{<{[([][])]{({}())<[][]>}}((<(){}>{()<>})<<<>{}>>)>}(((<{<>{}}<{}()>>)[((()()))[<<>{}>( +{{{[{{[(([{{[[{}<>]([]<>)]([(){}][[]()])}[{{[]<>}[{}()]}(<<><>>]]}][[([[[]<>]<<>{}>][{[]{} +<<{{{[{{[<({<<[]<>><{}<>>>}[[{()<>}{[]<>}]({(){}}{{}()})])[[(<<>[]>[[]])[{{}{}}[[]()]]]<(( +({{[{{{{<[[({[{}{}]{[]<>}}<<()<>>(()<>)>)<{{(){}]<()()>}{{(){}}[<><>]}>]<<({{}{}}<(){}>)([{} +{([[[<<([[{{[<()<>>(()<>))({<>[]}(<>{}))}{(<{}()>{()[]})}}([[{{}[]}<[]>]<([]<>){<>}>])]<{[[([]{})]{(<><>)[[]< +([<<([<[[({((({}[]){[]{}}){({}[])({}<>)})>)[(<{{{}{}}[(){}]}(([][])<{}<>>)><[[<><>]{<>{}}]{{{}[]}{[][]}}>)<[[ +{[<<{[<{[<{[[({}[])({}<>)]<<{}{}>{{}()}}]{[[<>{}]<[]{}>]}}(([(<><>){[][]}]([<>()]<()[]>))<[<()<>>(<><>) +(<[<[(({{<{<<[{}()]>(([]())<<>[]>)>}[{<[[]{}]([]<>)><{<><>}[()<>]>}{{{(){}}{[]{}}}}]>}}){{<{([<([]())({}()) +<<{({{<[<[{([{{}<>}{<><>}]([(){}](()[]))){[{()[]}<<>()>]({()()}({}()))}}]<[<<(())(()[])>>{(<{}<>>{ +{{{(<<[(<[([[[()<>]<()[]>]]{[[[]{}]{<>{}}]((<><>)({}{}))})]([(<{<>()}{[]{}}><<{}{}><<>[]>>)[<<()[]>((){ +[{(<<{[<<([{[<[][]>][{()()}({}())]}]([{([]{})<{}<>>}(<()>[{}])]{<([]<>)<{}{}>>(({}<>)<<><>>)}))>([[((<<>{} +[[(<({{(({{(<[{}<>]([]{})>{[<>[]]([][])})[(<[]<>><{}()>)[<{}()>(())]]}<[<[[]{}]<{}{}>>{<()()>({}<>)}]< +([(<({(<{{(((<[]<>>{<>()})<{[]{}>([]{})>)[<[()<>]({}[])>[(<>{})(<>[])]]){<((<>{}){()[]})><{<[]<>><<><>> +({{<({(<<({[[{<><>}]<<<><>>({}[])>]([<[]<>>[{}()]]{{<>{}}({}[])}}}<{({[]<>}({}{}))[(<>())(<>())]}((([]{}){[][ +(((({[(<([<[(<<>>[[][]])<{<><>}[{}<>]>]{({<>()}(<><>))(({}<>))}>])<([[<<()<>>{[][]}>]<[[[][ +[<<(([({((<<{({}())}[(()()){[]<>}]>>){({<[{}{}]<<>()>>{<{}()>}})})})])[[<((<[{([<>()][{}[]])((()[]) +{{{<{[[(<[{{<<{}()>[{}<>]>{({}{})<()>}}(<[(){}]>(<(){}>[[]]))}(<<[[]{}}({})><[<>[]]{<><>}>><[<[]<>>{<><>}]((( +{<[[<[{{<((<[<<>()><[]{}>][<{}()><{}{}>]>)[[[[[][]]<()[]}][([]{})(()())]]])>[[((<{[]<>}<()>><[{}[]](<><>)> +<{([{[{[{([({<{}[]>([]<>)}[[[][]]<()()>])[<[{}()]>{<<><>>(()<>)}]]){({(<[]<>>([]<>))[(<>){ +[{{<(({<{[{<{({})(<>[])}({<><>}{[]()})>(({[]<>}(<>{}))[{()()}{{}<>}])}{<{{<>()}<[]{})}(<()><( +{(<[{[[{{<(([{{}{}}<()[]>]<[<><>]>)([(<><>)])){<<{[]{}}[[][]]>{[[]<>]{<>{}}}>[<<[][]><<>()>><<{}<>>[<>()>>]}> +<[{{(<<{{{[[{<[]<>>(()<>)}<{()<>}{<>[]}>]<[<()()>]{<<>>([]())}>]{([<<>{}>({}<>)])[({{}}{[]<>})[ +<<[[<<[(<{{{[{()[]}([]())][[[]]((){})]}}(([{[][]}[()()]]<<()>[{}[]]>))}>)<{({{<[[][]]{()()}><<{}{} +{[<(([([{({([({}()){<><>}][((){})([]())])[<<()<>>(<>())>{[[]()}(()[])}]}<{{<(){}>{()[]}}((<><>){{}<>})}(( +{(({([[{{((([<()()>([][])]<<{}[]>[<>]>)(<{<><>}[{}{}]>[[<>{}]]))[{<<[][]>[()]><[{}{}]>}{<([]<>)([]<>)>}] +(({[[<<<{[{([(<>[])<()[]>])(<([])({}{})>)}[{(<<>{}>({}<>))}<<[<>[]>[(){}]>(<{}{}>)>]]{([[<<>()> +[{[[{([({[{(({()[]}[[]<>])<[[]<>]([])>)([{{}{}}[{}[]]]<{{}<>}>)][<(<<>[]>{<><>})<(()<>){[][]}>>[[{[]{}}([]()) +([[{({([({<<(<[]<>>)[{()[]}<<>()>]><<{<>()}<<>[]>>[[<>{}]<<><>>]>)<<<<<>[]><<><>>>[[[]]]>[{ +[{{([(<[[<{((([]{})[()()])[<[]{}>[<>()]])<[{()()}(<>{})]({[]}[<>{}])>}((<{[]()}>)({<{}{}>[ +[[[{{[[[<<(<(({}())[<>{}])<{{}<>}(()())>>[[((){})][[[]{}]{()}]])>>{[(<([{}{}]({}<>))([{}()]<<>[]])>({< +{<{({(({{{[{<(<>{})>{<{}<>>(<><>)}}<(<[]()>[[][]])<[{}()]>>]<{{<()[]>{{}()}}{<{}[]>{{}{}}}}{{<()<>><<> +[{[<{[<<({(<{{<>()}({}<>)}<(<>[])[()()]>>{{{<>()}{()[]}}})<{<({}()){<>{}}>({<>[]}({}())]}>}( +[[{<([((({([{{()[]}<[]{}>}({<>[]}[<><>])]{{<[]<>>[<><>]}[{()()}[<>[]]]})}[{[<{()()}(<>))<({}){()<>}>]([ +(<(<{[{<({[{{{[]()}({}{})}{<{}[]><<>{}>}}]})>}<{[[(<[<[]{}>([]())](<{}>[()()])>)][(([{[]<> +<<<<{<{<<[<({<[]>({}<>)})<[{[][]}[[]()]][[{}<>]<{}[]>])>([(([][])[[]()])<[{}[]]{[]{}}>](<[{}()]{{}()} +({[[([([<{[[({()[]}[<>[]])[<[][]><<>{}>>][(({}{})(<>[])){(<>[])}]]<{{{{}()}[<>[]]}<[[]<>]([]())>}>}{<{({ +((<{<{{[((<<<(()<>)<[]{}>>[<[][]>[(){}]]>>)){([[[[<>]({}<>)]{{{}<>}([]{})}]<[<<>()>(()())][{[]{}}{[]<>}]>] +{<<{{[({[<{{(<{}[]><<>[]>)([[]()])}<[{{}{}}{<><>}]<({})<{}()>>>}<[([{}<>][<>{}])](<<{}[]><< +{[{[[<<[(({[(((){}){<>[]})]([[<>[]]][<<>{}>[<><>]])})({<[({}[])<{}[]>]>}<(<(()<>)[[]{}]][[()()][<>< +((<<[({<{[[({<[]()>([]())}[<[]{}>(()<>)])]<<{[{}[]][<>()]}><<{<>[]}[()<>]>[<[]{}>{[]()}]>>][(({[<> +{{<[{<([({{([<<><>>{{}()}][<[][]>{[]()}])[<[[]]{[]<>}>]}(({<()[]>[{}()]}<[[]<>]{<>()}>))})])<{[{<[<[{}{}] +[(({({[[<{(<{{<>[]}[()<>]}((<>{})<<>[]>)>{[<(){}>{{}<>}]])[<{(<>[]){{}[]}}([()()][<>[]])>((({}()) +{(([<{([[{([<<{}()><[][]>>[(<>()){()[]}]]<[<()()>[{}]]([{}<>][{}[]])>){<{[[]<>]<<>{}>}{([]{})}>[([ +<{[{({([(<<{<[()<>]<(){}>>(<()>{[][]})})>{[([<{}{}>[<>()]])[{([]()){<>{}}}<<{}<>>>]]})])([<(([{<(){}>[{ +<<<(([<[{[[<{({}()>{[]()}}>]]([<[[()][{}[]]]<[()()]{(){}}>><<({}[])(()<>)>{(()[])}>]{[(<[]()>)[{[]{}} +(({(([[<{(<{[(<>[]]]}<(({}{}))([[][]]([][]))>><[{(()())[()<>]}<{(){}}>]>)((<{{()[]}{<>()}}<([ +[[[{<[{<[<[[{(<>{})<[]()>}{(()[])[<>()]}][<({}()>[<><>]>{<[]{}><[][]>}]]><<{[{(){}}{{}{}}][<<>< +((([<<((<[{(([<>[]]([]{}))([<>]{<>()})}([[(){}][[]()]][(<><>){{}()}])}{[<[()<>]({}<>)>[<{}{}>]][<(<>())<[] +{<<{([(((([{<[()]{(){}}>{{{}[]}}}[{(()[]){()<>}}{<<>>[<>{}]}]]<[{(<>())(()[])}]>){[[<({}{})[()[]]>{[{}( +([{(({([([(<<{<>[]}<{}<>>>[[[]{}]<[]{}>]>((([]<>)<<>[]>)[[(){}]]))[<{{<>{}}}[(<>}{()()}]><{<<>{}>< +{(<[<{({([{{([[][]]>({()()}[[][]])}[<(()())([]())><(<><>)([]{})>]}[(({{}{}}{{}<>})([<><>]<()[]>))] +((<(<<{{{{<{{(()<>)({}{})}{{<>[]}<()[]>}}<<<[][]>(()[])>[[()[]][[]()]]>>[[{{[]{}}[[][]]}][<[()[]]{[]<>}> +(((<(<<<{{<{<{{}{}}(()<>)>[((){})[()[]]]}{[[<>[]]{{}()}][{()[]}({}{})]}>}}>{[{<<{[{}()]}<[[]()] +({(([[<{[{<(([[]()](<><>))[<{}[]){{}()}])(<[()[]]<[]()>>[<{}<>>[()<>]])>[<[[()()]<<>[]>][([]())([]{})]>{{<<>( +[[(([((([<[[<{{}<>}({}[])>[([]<>)[()[]]]]<[(()())({}<>)](([]<>)<[]()>)>]>[{{{(()<>)}<{[]()} +(<(({[<<{((<[[[]()]{{}<>}]<<{}()>({}[])>>)<{{({}){()()}}<{[][]}>}>){[(<<[]<>>[{}{}]>{{(){}}{[ +{<(<(<<{<(<{{[{}[]]([]{})}([{}[]](<>))}[([<>()]{[]{}})<({}<>){<><>}>]>[{<[[][]]>}[<[<><>](()())>(([ +{(<(<{<(<<{{{<(){}>}({[][]}{{}{}})}}<(<<<>()>[(){}]>(<{}()>{{}}))>>[{{[{[]{}}(()[])]}[((<>())<< +([((([[{([{<<((){})>{{<>}{[]()}}>{[((){})<()[]>]<{[]{}}[(){}]>}}{((<{}()><{}>)){{<{}[]>[[] +{{{(<<{((([[{(<>{})({}())}[<[]()>({}{})]]<<[{}]>{[[]()]{()[]}}>]{({<()[]>((){})})<{<[]<>>{{}{} +(<<[[([{{(<<({{}}[{}{}])<([]{}){<>()}>>>(({<()()>({}[])}<<{}()>[(){}]>)))[{([{<>[]}([]())]([()()][<>{}])){ +<(<(<<<([[[(<{{}[]}{{}[]}>[[<>()]{()[]}])]<{({{}()}{{}<>})<({}[])([]<>)>}{(({}[])(<>{}))((()<>)<[]()>)}>](< +<<{(([<([[[[<{<>()}({}[])>{((){})[{}()]}]}[<{<<>[]><[]<>>}<[{}<>]{{}()}>>{{(()())[()[]]}{<()<> +(({<<[{([{<(([{}[]][(){}])[<[]()>(()<>)]}>}<[(<{{}()}{{}<>}><[[]()][[]()]>)]<(<(<>())>({<>[]})){[{(){}}<{}{}> +({[[{[{{<(([[[{}{}]<(){}>]((()[])(()[]))]({([]<>)({}{})}{<<>{}>{<>}}))<{(<[]{}>(()()))[[()[]](<><>) +([{[{{<{{(<[((<>){[]()})(({}())[<><>])]>({[(<><>}{{}[]}]{(()[])[()]}}{([[]{}](<><>))<<{}[]>(<>())>})) +<[(([[[<{[[(({()[]}<<>[]>){{<><>}<(){}>})<([()]{<>[]}){{<>{}}}>]]<[{{([]())({}{}>}<{[][]}<<>{}> +({(<{<([[<{[<{[]()}[{}<>>>[[(){}][[]{}]]][{[()<>][<><>]}{<()()>}]}<({{(){}}<()()>}{[<><>](<>())})[( +([({<[<[{({<<[<>{})[<><>]>{{[]{}}}>[[{()<>}([]{})](({}<>)<<>>)]}<{<<<>>{()[]}>}<{<[]()>}>>)(<<<{ +({<{[[({[([{[{[]{}}{[]<>}](((){})({}()))}]([<{<>{}}{()<>}>{({}{})(()())}])}]<{({{<()[]>{()}} +({[[{[((<[[[{<<>[]>(<>[])}]]{<({{}[]}){[{}{}]<<>()>}>[([<>()]{{}()})(<()()><<>[]>)]}]>(<([{<[]{}>(()())}<{[ +[<<<{<({({{<{[<><>]{{}<>}}{<(){}>}>}})})[<<[{[{({}{})<<>>}[{[]}(()())]]{{([]())([]())}{({}[])[<> +<<<[[((<{{<(((<>[])(()))<<<>()><<>{}>>)<[{<>[]}{(){}}]{[{}]({}())}>>}}[[((({()}{[][]}){<{}[]>} +(<[<({[[{[<<[(<>[])]([[]<>]<[]{}>)>({(<>[])([]())}<[{}<>)<<>{}>>)>[{<[[][]]><[{}<>]<[]()>>}<<[ +{[({<<<<(<([{{[][]}}]{<{<>()}<<>()>>[({}())<[]{}>]})>)>>><<<{{((([{}[]]<<><>>)[[[][]]({}[])])<{<{}{}>( +[[[({((({<([({[][]}{[]})<({}[]}[{}{}]>][([[]()]([]()))<<()<>>>])<[<<()[]><()[]>>]>>([<<{[]()}{[][]}>< +[{{<<<({<({(<<<><>>>{(<>[])[<><>]})<(<<>{}>(()()))[{{}()}{(){}}]}}[(<{[]()}<[]<>>>({<>{}}[{} +([<<{<[{([<[<{<>{}}<[]{}>><([][]>>][{([][])[()()]}<<[]<>>>]>][{<({<>()}<[][]>)[[[]<>]{()<>} +({<<[<(<<<(<((()<>)<{}<>>)>)<<([<>{}][<>[]])<<()()>({}[])>>([(()())(()<>)]{{{}<>}<[]{}>})>>>>[ +<{[{<[[<[(<{({<><>}{[]<>}){<[]{}><()>}}({({})[[]{}]})>{(<{[]<>}{<><>}>([{}[]]{<><>}))(<<{}()>[ +<{{<[[({{{<({<[]()>[{}[]]}<{<><>}{()<>}>)[<<[]{}>{{}<>}>[<<>[]>((){})]]>}}(<[[(([][])[{}<>]){(()<>)[()[]]}](( +[{[{<[(({{({<{{}{}}<()()>>(<[]<>>{<>[]})}(<<()[]>{()<>}>))<<<[{}{}]<<>[]>>>[[{[]{}}]((()<>){<> +{[[<{({[<(<((<{}[]>){[{}[]]})[{[{}{}]({}())}]>(<{{()<>}(<><>)}[(<><>)<[]{}]]><{<()[]>([][]) diff --git a/2021/day-10/linting.js b/2021/day-10/linting.js new file mode 100644 index 0000000..1dc1260 --- /dev/null +++ b/2021/day-10/linting.js @@ -0,0 +1,59 @@ +const pairs = { + '(': ')', + '[': ']', + '{': '}', + '<': '>' +} + +const lintLine = (line) => { + let expected = '' + let pos = 0 + while (pos < line.length) { + const char = line[pos] + + // if opening bracket, add mate to the start of expected list + if (pairs[char]) { + expected += pairs[char] + } else { // if closing bracket + // if expected closing, clear from the expected list + if (expected[expected.length - 1] === char) { + expected = expected.slice(0, -1) + } else { // otherwise, found an error to report + return { + char: pos, + expected: expected[expected.length - 1], + found: char + } + } + } + + pos++ + } + + // if we run out of characters in the line, that means it is + // incomplete, and we need to provide an autocomplete suggestion + if (expected.length > 0) { + // Reversing the 'expected' string gives us the autocomplete suggestion + return { + suggestion: [...expected].reverse().join('') + } + } +} + +const lintAll = (instructions) => { + const errors = instructions.map(lintLine) // lint each line + .map((error, idx) => { + return { ...error, line: idx } + }).filter((report) => !!(report.char) || !!(report.suggestion)) // remove lines without errors + + console.log(`Linting found ${errors.length} errors in ${instructions.length} lines.`) + // console.debug(instructions) + // console.debug(errors) + + return errors +} + +module.exports = { + lintLine, + lintAll +} diff --git a/2021/day-10/linting.test.js b/2021/day-10/linting.test.js new file mode 100644 index 0000000..842107f --- /dev/null +++ b/2021/day-10/linting.test.js @@ -0,0 +1,123 @@ +/* eslint-env mocha */ +const { expect } = require('chai') +const { lintLine, lintAll } = require('./linting') + +const badChunks = [ + '(]', + '{()()()>', + '(((()))}', + '<([]){()}[{}])' +] + +const testData = `[({(<(())[]>[[{[]{<()<>> +[(()[<>])]({[<{<<[]>>( +{([(<{}[<>[]}>{[]{[(<()> +(((({<>}<{<{<>}{[]{[]{} +[[<[([]))<([[{}[[()]]] +[{[{({}]{}}([{[{{{}}([] +{<[[]]>}<{[{[{[]{()[[[] +[<(<(<(<{}))><([]([]() +<{([([[(<>()){}]>(<<{{ +<{([{{}}[<[[[<>{}]]]>[]]` + +const autocomplete = { + '[({(<(())[]>[[{[]{<()<>>': '}}]])})]', + '[(()[<>])]({[<{<<[]>>(': ')}>]})', + '(((({<>}<{<{<>}{[]{[]{}': '}}>}>))))', + '{<[[]]>}<{[{[{[]{()[[[]': ']]}}]}]}>', + '<{([{{}}[<[[[<>{}]]]>[]]': '])}>' +} + +describe('--- Day 10: Syntax Scoring ---', () => { + describe('Part 1', () => { + describe('lintLine()', () => { + it('finds instnces of closing brackets that mismatch the opening brackets', () => { + expect(lintLine(badChunks[0])).to.deep.equal( + { + char: 1, + expected: ')', + found: ']' + } + ) + expect(lintLine(badChunks[1])).to.deep.equal( + { + char: 7, + expected: '}', + found: '>' + } + ) + expect(lintLine(badChunks[2])).to.deep.equal( + { + char: 7, + expected: ')', + found: '}' + } + ) + expect(lintLine(badChunks[3])).to.deep.equal( + { + char: 13, + expected: '>', + found: ')' + } + ) + }) + }) + describe('lintAll', () => { + it('finds all lines with linting errors', () => { + const errors = lintAll(testData.split('\n')) + .filter((err) => (err.char)) + + expect(errors.length).to.equal(5) + expect(errors[0]).to.deep.equal({ + line: 2, + char: 12, + expected: ']', + found: '}' + }) + expect(errors[1]).to.deep.equal({ + line: 4, + char: 8, + expected: ']', + found: ')' + }) + expect(errors[2]).to.deep.equal({ + line: 5, + char: 7, + expected: ')', + found: ']' + }) + expect(errors[3]).to.deep.equal({ + line: 7, + char: 10, + expected: '>', + found: ')' + }) + expect(errors[4]).to.deep.equal({ + line: 8, + char: 16, + expected: ']', + found: '>' + }) + }) + it('provides autocomplete suggestions for incomplete lines', () => { + const data = testData.split('\n') + const errors = lintAll(data) + .filter((err) => !!err.suggestion) + + expect(errors.length).to.equal(5) + errors.forEach((err) => { + expect(err.suggestion).to.equal( + autocomplete[data[err.line]] + ) + }) + }) + it('skips lines without errors', () => { + const errors = lintAll([ + '[]', + '[()]' + ]) + expect(errors.length).to.equal(0) + }) + }) + }) +}) diff --git a/2021/day-10/scoring.js b/2021/day-10/scoring.js new file mode 100644 index 0000000..45c745c --- /dev/null +++ b/2021/day-10/scoring.js @@ -0,0 +1,27 @@ + +const findMiddleScore = (scores) => { + // According to specs, there's always an odd number of items in the list, + // so we're safe to divide by 2 and round down to get the desired index + return scores.sort((a, b) => a - b)[ + Math.floor(scores.length / 2) + ] +} + +// How many points each character is worth in autocomplete scoring +const pointValues = { + ')': 1, + ']': 2, + '}': 3, + '>': 4 +} + +const scoreAutocomplete = (suggestion) => { + return [...suggestion].reduce((score, char) => { + return (score * 5) + pointValues[char] + }, 0) +} + +module.exports = { + findMiddleScore, + scoreAutocomplete +} diff --git a/2021/day-10/scoring.test.js b/2021/day-10/scoring.test.js new file mode 100644 index 0000000..0ee2fe7 --- /dev/null +++ b/2021/day-10/scoring.test.js @@ -0,0 +1,36 @@ +/* eslint-env mocha */ +const { expect } = require('chai') +const { findMiddleScore, scoreAutocomplete } = require('./scoring') + +const scoreData = [ + 288957, + 5566, + 1480781, + 995444, + 294 +] + +const autocompleteSuggestions = [ + '}}]])})]', + ')}>]})', + '}}>}>))))', + ']]}}]}]}>', + '])}>' +] + +describe('--- Day 10: Syntax Scoring ---', () => { + describe('Part 2', () => { + describe('scoreAutocomplete()', () => { + it('takes a single autocomplete suggestion and scores it', () => { + autocompleteSuggestions.forEach((suggestion, idx) => { + expect(scoreAutocomplete(suggestion)).to.equal(scoreData[idx]) + }) + }) + }) + describe('findMiddleScore()', () => { + it('takes a list of scores and returns the middle entry after sorting', () => { + expect(findMiddleScore(scoreData)).to.equal(288957) + }) + }) + }) +}) diff --git a/2021/day-10/solution.js b/2021/day-10/solution.js new file mode 100644 index 0000000..c20343f --- /dev/null +++ b/2021/day-10/solution.js @@ -0,0 +1,51 @@ +const fs = require('fs') +const path = require('path') +const filePath = path.join(__dirname, 'input.txt') +const { linesToArray } = require('../../2018/inputParser') +const { lintAll } = require('./linting') +const { scoreAutocomplete, findMiddleScore } = require('./scoring') + +fs.readFile(filePath, { encoding: 'utf8' }, (err, initData) => { + if (err) throw err + + initData = linesToArray(initData.trim()) + + const resetInput = () => { + // Deep copy to ensure we aren't mutating the original data + return JSON.parse(JSON.stringify(initData)) + } + + const part1 = () => { + const data = resetInput() + const points = { + ')': 3, + ']': 57, + '}': 1197, + '>': 25137 + } + + const errors = lintAll(data) + + // Score the premature closure errors + return errors.filter((err) => !!err.char) + .reduce((total, error) => total + points[error.found], 0) + } + + const part2 = () => { + const data = resetInput() + // find the incomplete line errors + const errors = lintAll(data).filter((err) => !!err.suggestion) + + const scores = errors.map((err) => scoreAutocomplete(err.suggestion)) + + return findMiddleScore(scores) + } + const answers = [] + answers.push(part1()) + answers.push(part2()) + + answers.forEach((ans, idx) => { + console.info(`-- Part ${idx + 1} --`) + console.info(`Answer: ${ans}`) + }) +}) diff --git a/2021/helpers.js b/2021/helpers.js new file mode 100644 index 0000000..f1fdc6d --- /dev/null +++ b/2021/helpers.js @@ -0,0 +1,14 @@ +// Suppress logging +if (!process.env.DEBUG) { + console.debug = () => {} + console.log = () => {} +} + +module.exports = console +// module.exports = { +// debug: console.debug, +// info: console.info, +// log: console.log, +// warn: console.warn, +// error: console.error +// } diff --git a/README.md b/README.md index a1b8c0f..62370d1 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ ## Status ### 2021 -![](https://img.shields.io/badge/day%20📅-16-blue) -![](https://img.shields.io/badge/stars%20⭐-14-yellow) -![](https://img.shields.io/badge/days%20completed-7-red) +![](https://img.shields.io/badge/day%20📅-17-blue) +![](https://img.shields.io/badge/stars%20⭐-19-yellow) +![](https://img.shields.io/badge/days%20completed-9-red) ## Run the currently configured default day `npm start` diff --git a/index.js b/index.js index f4abd84..274e3b7 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -require('./2021/day-09/solution') +require('./2021/day-10/solution')