From c445f4073839779d80a9e7a8fc919f0c101ebe37 Mon Sep 17 00:00:00 2001 From: Anthony McLin Date: Thu, 16 Dec 2021 13:11:44 -0800 Subject: [PATCH 1/3] feat(2021-day-09): find the low points on the terrain Solves part 1 --- 2021/day-09/basins.js | 44 ++++++++++++++++ 2021/day-09/basins.test.js | 24 +++++++++ 2021/day-09/index.js | 3 ++ 2021/day-09/input.txt | 100 +++++++++++++++++++++++++++++++++++++ 2021/day-09/solution.js | 38 ++++++++++++++ index.js | 2 +- 6 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 2021/day-09/basins.js create mode 100644 2021/day-09/basins.test.js create mode 100644 2021/day-09/index.js create mode 100644 2021/day-09/input.txt create mode 100644 2021/day-09/solution.js diff --git a/2021/day-09/basins.js b/2021/day-09/basins.js new file mode 100644 index 0000000..017b4e3 --- /dev/null +++ b/2021/day-09/basins.js @@ -0,0 +1,44 @@ +const findLocalLows = (data) => { + const lows = [] + const rows = data.split('\n') + let checked = 0 + + const isLow = (val, col, row) => { + // Collect points in each cardinal direction + const points = [] + // TODO: If supporting diagonal checks, use this logic instead to loop + // for (let x = -1; x <= 1; x++) { + // for (let y = -1; y <= 1; y++) { + // if(x != 0 && y != 0) + // if(rows[row + y] && rows[row + y][col + x] + // } + // } + if (rows[row - 1] && rows[row - 1][col]) { points.push(parseInt(rows[row - 1][col])) } + if (rows[row + 1] && rows[row + 1][col]) { points.push(parseInt(rows[row + 1][col])) } + if (rows[row] && rows[row][col - 1]) { points.push(parseInt(rows[row][col - 1])) } + if (rows[row] && rows[row][col + 1]) { points.push(parseInt(rows[row][col + 1])) } + + // NOTE - if the value is the same as a neighbor, + // that isn't counted as a low (even though together, they can be a low) + // ... this might be a concern for part 2 .... + return (val < Math.min(...points)) // value should be lower than any other points + } + + rows.forEach((row, rowIdx) => { + for (let c = 0; c < row.length; c++) { + const cell = parseInt(row[c]) + if (isLow(cell, c, rowIdx)) { + lows.push(cell) + console.debug(`Found low at ${c},${rowIdx}: ${cell}`) + } + checked++ + } + }) + + console.debug(`Checked ${checked} cells`) + return lows +} + +module.exports = { + findLocalLows +} diff --git a/2021/day-09/basins.test.js b/2021/day-09/basins.test.js new file mode 100644 index 0000000..b874326 --- /dev/null +++ b/2021/day-09/basins.test.js @@ -0,0 +1,24 @@ +/* eslint-env mocha */ +const { expect } = require('chai') +const { findLocalLows } = require('./basins') + +const testData = `2199943210 +3987894921 +9856789892 +8767896789 +9899965678` + +describe.only('--- Day 9: Smoke Basin ---', () => { + describe('Part 1', () => { + describe('findLocalLows()', () => { + it('finds the local low points in the terrain', () => { + // low points in the testData are 1, 0 on row 0 + // 5 on row 2 + // 5 on row 5 + const results = findLocalLows(testData) + expect(results.length).to.equal(4) + expect(results.reduce((a, b) => a + b)).to.equal(11) + }) + }) + }) +}) diff --git a/2021/day-09/index.js b/2021/day-09/index.js new file mode 100644 index 0000000..af7e035 --- /dev/null +++ b/2021/day-09/index.js @@ -0,0 +1,3 @@ +// eslint-disable-next-line no-unused-vars +const console = require('../helpers') +require('./solution') diff --git a/2021/day-09/input.txt b/2021/day-09/input.txt new file mode 100644 index 0000000..44432ed --- /dev/null +++ b/2021/day-09/input.txt @@ -0,0 +1,100 @@ +5434456789899876598943212349988734679896554212468998765634987654567895454569876745567898765987654678 +4421345997789997987894103458976521398765432101457897664529876543988987353478965434456799974398766789 +3210237896698999876789214567895320129878543212346987543212989432499976212389876521234589867219878999 +8732456934567987655678925679976543234999657323657898952101296521276895423467987420145678954323989987 +7545678923979996544367899899997665379898775454767899843212398730145789439999876521266789765439896895 +8656989019899985431257912978989876456789896569898998754324579821234679598787987632348999976598765434 +9767893198769876542349943969878987567891987678959799867465679932348789987656899973467898899989754325 +9899954599356987653567899858767898998910198789345689879876898655459892199767898765989987678978965434 +0949765983245898974567986745656789429963239891256998989987998798767891019898999899899876569867896645 +1239899874123798765678965432345678998754356999969897896998919999898942129919899989798765432956789896 +2456999763235679976789876521012899569896567987898776645789423998989763298901698978679654321987899987 +3567998654345789697894997863246789345989679976897655434678999887678954397992497761598767210198989999 +4568998767658994598932987654959896559878998965689543212789989756545895986789986550349989321239878934 +5679109978767893459891098799898998698766987654599652101299876542136789975498765431256999932349867995 +6789299989878932398789129988797789797654598543688943212389987651012898996329765432367899893998656789 +7898989899989321239679939878646678976543987654567894345678999954123456789319876783456987679899543454 +8987674789996510398567898965434567895432398769878997656789298893256567892101989865597976545788932123 +9876543668897932987678987654325478999421239878989989897892196799347878943212399876789876434567893015 +6976542456789893498789798843412345678910129989095678998943985978958989954323498989899854323479992134 +5988764567898789999895699752103456989321298792134578949959874567899699895434597898998769436589789645 +3299985998976679891934987653212367895433397654236689939898765878934598789545986567899898998995679756 +4399876789765456790949998654354567976564579864346789795789876989423987678959876456789987999754598967 +5989987898874345689898998765667678987678679875467997674898997893219876579898965345699876899643457999 +9874399987654234889767249879799899398788799986578998543456789979101985498787976458798765678952348688 +9965456798982124678954123998989921299899899897789019432349892356919876987656897569987654569861234567 +9878767899971012567893299897678930987943945798899929656478901237895988995434789978999543498990195688 +4989879998753223456789987684567899876542134689967898767567892348954599986523678989898921987989989789 +3292998987654345567899876543454979987698745798756789888678943456795678987434567898797990196578878990 +5101997699865456878999987952123567898987656987645999999799764789989799398646678987686789987458767891 +3219876439876767989789854321018798909998789398739898998999875699978989109798789999575679654345656789 +4923987421988878994698765432134689212999899219898787887689987898967878919899899998434598743212345699 +9894996210199989543569876987656789329896998998997645986567898987657567894902999896565679754103456999 +8789765434567896532979987899769895498785987987987432345457899996543456793219899789678998765214667898 +7679987545698987649899998959878999597654596576796541012345678987532345789398798698989439886356778987 +6567899756789498998787899543999788999869989435987782134456989997431234678987654587892129987487899876 +1456789987891349987656987682101677896998764324598943245667892986520345679976743476789098987598989988 +2367891098942998876435699899323456795109875455799876368998901975421234567895432125678987798679769899 +3458932989959897652123799998754789894298989569899965467999219876532345689984321014789976689789543767 +6567899876798789943254889879875678989997898698999876567894334998765456796975432423899865579899632156 +7678999865987656798765679765996899569876679987987987678999549769976789895496676534998784465978943234 +8789489954698747899898789894597943456965569876756798989998998756987899932398987545797653234567894965 +9896567893497656789989899989698952349893457954348899999867897645698998651239998676789543123456789896 +4989678912399788997678969878999876599789569943235978987656798434569679962345999987898654234567898797 +3479989101989899234579953967899997987679979754101567896546795320197599843469899998939764346678999689 +2567893219876920123498842545798798996589898765243489998657896431989987654598788999323976487989898567 +1457954998985431245987721234989659987456799876756678998798986549879999867998687896534987578998766456 +0345699877896545679876510129878943298589890987987899989899598698768899979896556789649898789789654233 +1258789565987656798765431298967892129678992999398987667965498797658789998787445679998769897698763101 +3879895434598767899876532987656789098789979893219898548994349988546698987674324567898756989549874222 +4989999323569899987989749876545699989898767689456798656789239975234567896532014687996545678934965434 +5699888912479959876599898765434598878989954597997959897891098754123458976432123456789436789029896945 +6789767893569542987456999876523987654567893456789542998943987653012369897643234797897647892198789896 +7996556789698959898968998765219876543478932345698951019995698432123456797654365698998758943987668689 +8954345678987998759979897654327989657899321234567892129889997643264567899895776789019869959876547567 +9543287567896789543499789876456798798999872345678963498769898965459678999976798997998999898954323458 +8762123456795678932987678976578979899698765456789954989656789878678989689987899756987889767895444767 +6571012387894239321998489987678967989459889578997899876545995989789996577998987645986563456976655678 +8432123456943145939876569998989345678967998789756798765634894399898895456879995429876432356897779899 +7543234579432019899998678989492235678979679897645987654329795210956789327467896539994321237998989931 +8654345678973298799659989976320136789989536965534598965418689632345893214357987698789435398949995432 +9765566899954989578945698765431246789997645984323459876324596543456789301236798797698945459539876543 +9878987899869876457936789987543657894987659876775767988765797786569895212545679987567896579321997654 +9989398998998765346897894697654769923498767987899878999896789897678954323598789498678987678932398895 +9899459987679976456789923498767878939999989998910989999987894998789765499989892349989698989993979976 +8798967943568989769893212459878989998791299879322398989998943769999876987878991234596439498789765987 +7667899652456899878976493967989399876689399965443987878999542356789989776456789949697321334599654598 +6545678943597956989987989898996598985545989987559876769897669467894597654345699898989210123498963239 +9436789894989439997899876789987987694234567897698765458789798978932987543248998767678933234987892134 +8997898789878998876799985689998975543123458998789987345678997989321997654367999856577894549876789045 +6889997679767897545989654567899984321012567899898753234789986899439898765456897845456789698765692156 +5679986545656989439876543678920995443227679998999864565699875778999789996567986434345698799754589267 +4798765434745778924987762399439876654535789587998765699789564567987678987698954323236789988643678979 +3987654321434567995699843489597987765678996476899876789895423459876569299899875210127999876512889989 +2398776410123456789987654578976598978789875345789987899964312346975489101999986321239899974323690198 +1459898531234587898798767678965439989999954234598798979975423769876594312398765432349798765434569297 +2699876542346678997649878789432123499999876345987659567897569878987694324569898543498659896565678956 +9989987843656789989432989896543034578989985456798741456898689989299789455689987654987545987876789245 +8978998654567897678921299987652145989979876787899432346789799892109896596798998965696534698987992123 +7767899765678976567932349998543267898765998998989753456899896789512987989987899878987623499698993434 +6456789878789995478945678997654379987654989999978999567899945695423499879876789989998834988549889645 +2348995999998989599656789998765459998769877898765688979998956789534598766545679199876549876534678956 +1256893212987876989767899869876567899898756987654567999986899899976987655326989298987698765423459867 +2367999323986765878998987656987878998999967898323457899875798999897898543201399987598789879210678978 +3478998939765654569999896545698989667989899999212345899964567898799939954912568898449899984321289989 +4989997898754343456789765434349893459879798987463456789643456789688929899893456789234989965434567896 +5678976798773212345899984321256789599965667896554569896532567894567998789789598894345679876565778965 +6899995987654301236789876532367899987654356987789678997321013789679876545699679965956789987876789954 +8987989998765312447897987688456789998543234598898789398432123678989999433488989989897999998998899895 +9766767989876493558965499786567896987652105679999891298743454569999878521367893198769899899899966789 +7654555679997989967894329897898945698543212589899999987655677678998765310456789976556789765732455894 +6553334899989879898965469989999334987654323456789678998766788889429896321367899896345678954621234932 +5432124578978956789876598976989219999768454579894599659877899995434975433698998765234579543210129891 +6543023569864345699997986745878998789879767699923589547998998989565986844589987654357897654521656790 +9632123479983234567898965434567899667989888789013478936669987879976987865679999866468999865632347891 +8743244569874347689999874323456965456795999993123569425459986567899898979789899998567899876774458942 +8654456798765456790198765201597896568954987654256989312398765458968799989897789219778945988765667899 +9987587899976569891239877332387899878943298965345993201239954349355689997956678909899434199876789978 +1098788998988789932545976545456956989652129876556789713498765693234598766434567899974321019998893459 +3129999987699896543489988758969139898761034987679899654569876789345679654323489998765432198779902345 +4534567898799987654578999767878956789432156798789998775678987895468798765434591249976545698654323456 diff --git a/2021/day-09/solution.js b/2021/day-09/solution.js new file mode 100644 index 0000000..6c469ba --- /dev/null +++ b/2021/day-09/solution.js @@ -0,0 +1,38 @@ +const fs = require('fs') +const path = require('path') +const filePath = path.join(__dirname, 'input.txt') +const { linesToArray } = require('../../2018/inputParser') +const { findLocalLows } = require('./basins') + +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().join('\n') + const lows = findLocalLows(data) + console.debug(lows.length) + // risk profiles is the sum of each height + 1, which is the same as adding the count of lows + return lows.reduce((a, b) => a + b) + lows.length + } + + const part2 = () => { + const data = resetInput() + // console.debug(data) + return 'No answer yet' + } + const answers = [] + answers.push(part1()) + answers.push(part2()) + + answers.forEach((ans, idx) => { + console.info(`-- Part ${idx + 1} --`) + console.info(`Answer: ${ans}`) + }) +}) diff --git a/index.js b/index.js index 23dbf0d..f4abd84 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -require('./2021/day-08/solution') +require('./2021/day-09/solution') From 8ec8e80282e97f509f43d089fbda25f16227b254 Mon Sep 17 00:00:00 2001 From: Anthony McLin Date: Thu, 16 Dec 2021 15:34:10 -0800 Subject: [PATCH 2/3] feat(2021-day-09): find the largest basins Solves part 2 I was naughty and didn't follow TDD for this one. Mistakenly thought the logic would be more terse than it ended up being. --- 2021/day-09/basins.js | 158 ++++++++++++++++++++++++++++++++++------ 2021/day-09/solution.js | 22 +++++- 2 files changed, 154 insertions(+), 26 deletions(-) diff --git a/2021/day-09/basins.js b/2021/day-09/basins.js index 017b4e3..3e2a4c1 100644 --- a/2021/day-09/basins.js +++ b/2021/day-09/basins.js @@ -1,33 +1,33 @@ +const isLow = (val, col, row, rows) => { + // Collect points in each cardinal direction + const points = [] + // TODO: If supporting diagonal checks, use this logic instead to loop + // for (let x = -1; x <= 1; x++) { + // for (let y = -1; y <= 1; y++) { + // if(x != 0 && y != 0) + // if(rows[row + y] && rows[row + y][col + x] + // } + // } + if (rows[row - 1] && rows[row - 1][col]) { points.push(parseInt(rows[row - 1][col])) } + if (rows[row + 1] && rows[row + 1][col]) { points.push(parseInt(rows[row + 1][col])) } + if (rows[row] && rows[row][col - 1]) { points.push(parseInt(rows[row][col - 1])) } + if (rows[row] && rows[row][col + 1]) { points.push(parseInt(rows[row][col + 1])) } + + // NOTE - if the value is the same as a neighbor, + // that isn't counted as a low (even though together, they can be a low) + // ... this might be a concern for part 2 .... + return (val < Math.min(...points)) // value should be lower than any other points +} + const findLocalLows = (data) => { const lows = [] const rows = data.split('\n') let checked = 0 - const isLow = (val, col, row) => { - // Collect points in each cardinal direction - const points = [] - // TODO: If supporting diagonal checks, use this logic instead to loop - // for (let x = -1; x <= 1; x++) { - // for (let y = -1; y <= 1; y++) { - // if(x != 0 && y != 0) - // if(rows[row + y] && rows[row + y][col + x] - // } - // } - if (rows[row - 1] && rows[row - 1][col]) { points.push(parseInt(rows[row - 1][col])) } - if (rows[row + 1] && rows[row + 1][col]) { points.push(parseInt(rows[row + 1][col])) } - if (rows[row] && rows[row][col - 1]) { points.push(parseInt(rows[row][col - 1])) } - if (rows[row] && rows[row][col + 1]) { points.push(parseInt(rows[row][col + 1])) } - - // NOTE - if the value is the same as a neighbor, - // that isn't counted as a low (even though together, they can be a low) - // ... this might be a concern for part 2 .... - return (val < Math.min(...points)) // value should be lower than any other points - } - rows.forEach((row, rowIdx) => { for (let c = 0; c < row.length; c++) { const cell = parseInt(row[c]) - if (isLow(cell, c, rowIdx)) { + if (isLow(cell, c, rowIdx, rows)) { lows.push(cell) console.debug(`Found low at ${c},${rowIdx}: ${cell}`) } @@ -39,6 +39,118 @@ const findLocalLows = (data) => { return lows } +const flow = (col, row, map, data, source) => { + // Don't test invalid points + if (col < 0 || col >= map.coords[0].length) { + console.debug(`${col},${row} is out of bounds`) + // Exceeds map horizontally + return { + map, + result: false + } + } + if (row < 0 || row >= map.coords.length) { + console.debug(`${col},${row} is out of bounds`) + // Exceeds map vertically + return { + map, + result: false + } + } + + // If the point is a peak, register and don't continue + if (parseInt(data[row][col]) === 9) { + console.debug(`${col},${row} is a peak.`) + // Peaks aren't part of basins + map.coords[row][col] = 'p' + return { + map, + result: false + } + } + + // If the point is higher than the source, we can't drain + // BIG ASSUMPTION here about equal-height points + if (data[row][col] >= source) { + console.debug(`${col},${row} is higher (${data[row][col]} >= ${source}). Water can't flow uphill.`) + return { + map, + result: false + } + } + + // If the point already mapped to a basin, don't recalculate its flow + if (map.coords[row] && map.coords[row][col]) { + console.debug(`${col},${row} is already known to be in basin ${map.coords[row][col]}`) + return { + map, + result: map.coords[row][col] + } + } + + // If we've reached a low point, stop tracing + if (isLow(data[row][col], col, row, data)) { + console.debug(`${col},${row} is a low point in basin.`) + // register a basin with an area of 1 + map.basins.push(1) + // mark the low point to the basin + map.coords[row][col] = map.basins.length - 1 + console.debug(`registered basin ${map.basins.length - 1}`) + return { + map, + result: map.coords[row][col] + } + // HUGE ASSUMPTION that each basin only has 1 low point + } + + console.debug(`checking where point ${col},${row} drains to`) + + // Check the next points in each cardinal direction + const drains = [] + let result = false + result = flow(col + 1, row, map, data, data[row][col]) // right + map = result.map + drains.push(result.result) + result = flow(col - 1, row, map, data, data[row][col]) // left + map = result.map + drains.push(result.result) + result = flow(col, row - 1, map, data, data[row][col]) // up + map = result.map + drains.push(result.result) + result = flow(col, row + 1, map, data, data[row][col]) // down + map = result.map + drains.push(result.result) + + const results = drains.filter((c) => c !== false) + if (results.length > 1) { + console.warn('Point has more than one possilbe drain.') + const uniqueDrains = [...new Set(results)] + if (uniqueDrains.length > 1) { + console.debug(drains) + throw new Error('Point drains into multiple drains. Data might be bad.') + } + // Otherwise, all drains go to the same basin, so that's the same as having 1 drain + } + if (results.length === 0) { + console.debug(drains) + throw new Error('Point is not the low, but has no drains. Data might be bad.') + } + + const basin = parseInt(results[0]) + + // Mark the point as belonging to the basin it drains into + map.coords[row][col] = basin + // Track the area of the basin so we don't have to recalculate it later + map.basins[basin]++ + + // return the findings recursively + return { + map, + result: map.coords[row][col] + } +} + module.exports = { - findLocalLows + findLocalLows, + flow } diff --git a/2021/day-09/solution.js b/2021/day-09/solution.js index 6c469ba..b80022f 100644 --- a/2021/day-09/solution.js +++ b/2021/day-09/solution.js @@ -2,7 +2,7 @@ const fs = require('fs') const path = require('path') const filePath = path.join(__dirname, 'input.txt') const { linesToArray } = require('../../2018/inputParser') -const { findLocalLows } = require('./basins') +const { findLocalLows, flow } = require('./basins') fs.readFile(filePath, { encoding: 'utf8' }, (err, initData) => { if (err) throw err @@ -24,8 +24,24 @@ fs.readFile(filePath, { encoding: 'utf8' }, (err, initData) => { const part2 = () => { const data = resetInput() - // console.debug(data) - return 'No answer yet' + let map = { + coords: [...Array(data.length)].map(() => Array(data[0].length)), // allocate a map + basins: [0] // fake first basin with no area to avoid having to figure out JS loose-type false/truthy and <> bullshit + } + + for (let x = 0; x < data[0].length; x++) { + for (let y = 0; y < data.length; y++) { + console.debug(`Starting flow trace at ${x},${y}`) + map = flow(x, y, map, data, 9).map // 9 is a magic number for assuming we start with fresh drains + } + } + + console.debug(`Found ${map.basins.length} basins.`) + + // Multiply the area of the 3 largest basins + return map.basins.sort((a, b) => a - b) + .slice(map.basins.length - 3) + .reduce((a, b) => a * b) } const answers = [] answers.push(part1()) From 82914d0ee92a0ae9ccec9989d5525338e5e91da4 Mon Sep 17 00:00:00 2001 From: Anthony McLin Date: Thu, 16 Dec 2021 15:37:18 -0800 Subject: [PATCH 3/3] test: reenable temporarily disabled tests --- 2021/day-09/basins.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2021/day-09/basins.test.js b/2021/day-09/basins.test.js index b874326..e3486a7 100644 --- a/2021/day-09/basins.test.js +++ b/2021/day-09/basins.test.js @@ -8,7 +8,7 @@ const testData = `2199943210 8767896789 9899965678` -describe.only('--- Day 9: Smoke Basin ---', () => { +describe('--- Day 9: Smoke Basin ---', () => { describe('Part 1', () => { describe('findLocalLows()', () => { it('finds the local low points in the terrain', () => { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy