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/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')
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: