diff --git a/src/10/enput.txt b/src/10/enput.txt new file mode 100644 index 0000000..7bb314a --- /dev/null +++ b/src/10/enput.txt @@ -0,0 +1,2 @@ +230,198,140,15,99,162,233,78,40,174,240,37 +22c049ca63c26db4192d251a22fc6f8871f86b12ed3b28afb5aadfc33d58a4a5343f759fa6fbc817039e99c36b8a8ad9e12b8e94fac8457768daa408b11326a2187ad65c62447ec07fa45e3d17ee398f785c87fb675cac4ea973bb2c40804110f63ac213f098d7c41fe48bb044d14f312423f6451bb09636c21117df7a72b4dbbe229f023c3fec974cf8a1efe4124af5079881697599f49aa64c8a00e8b457b5ac9ed30bfcdafae2930e542b5676258dd2fb83c124a0dcad4a4e3d3b4cd760d42db5171cce9b033eb76f0f1dd047bef7971a9b76ee45bd7c490408d2e92474fe3937a64b43f806429dd5bb9ee1cde3b5ce374e63d8f8c306a7258e11fe7e3db4bed039d7f83d3fd4f8f572dfbb2536eff58677128ada123e075e86ac692b73e445e33a8118116240470fa256e462c1ad6397a6f80b267b457dd427710e2b29017bb86fa2e56cc6f0fd8e8d9a9d6bf88216edbe64c8ff959e6928b133e71d14af3d1916eeee88eabfd5baa9b910075a4dde279d633ca34d903bfe19c30cb8af52a855bdbf4eb2d2eb5f2d534253288c68ea80c03a58c979b4e90494cce896362e865bafad7125c54e26cf9a51377760891970d0c6e419de07077ce5b945e4f050824a9ee5941d90bef6509c99b7e00c9b935eae64bfcec6d094f2fc27ee05b03e1b2ad585df6348151005b350e3626a5cffcd876f95daaab58fe5372f7b9c7bd46c325529c569ee859d6744a24bdd759709a72b82b49f0f5b35924481bda6bbf8b3cbc2ea20a3de6d9b2b1b97dd595425685d1ee1cffaf3e6cb12e7b6b064c090d03d68d4983f25a6b70ce30c938f8405a256309c30de718d0cbdea0f5ccfa0648c55efac58578cba95c00fc0b8ea03dbd19d8c0fb4c27d5f4fa5a2d2178cf126b1b109db01023663b42a81386838c191735cd71f30eb7a394c5f571887f1e076a7973e768211b97734c28642632995e65f10da2023c69b7ce68c7163425a8c2a809bc9dc93d4f45586fb78a7bbb45b6d8c4acae8b34d0fb40ffd64c9aa214a2f58262417a5f8254bfc7127a6bac16bc4e561900481f1e7a4e688be5e14674ce0f4333e43242a2d12512fd8ef946da06eef7164a33d8a1fd23ce23a2b25b63f3a5880336010532a5cbeeb13a697e0b1a3c98b6742c6c479ede192664a77e51e4f0d4994129ed9e6b9fb2a35c3cf23cde5c1958d8b0acc55ae77c04c699a69ce54ac02bf1075d3bb284bd4eba1d9352d0e40695f9d047baf3fb7c847a07efed03d1242526b9f39158763a08da950ef4ed4d3f36d09f486106d078003fbef236238e16705404eff7b6177e28864b13d04b24ce1423cc58c29db0812cca107eb8ce3bb4b56e46d908c2b75c7fd1499cf012fc7e201b1a65e22e086c67e9967d8c0416c5cbc07f13bf73c3c22b00da97badaaa0605800f5b5e5c59d69dbd8e2ab94dab6f76127a83de7fb509c07b6477b7593bfc888ffce9b3623e84e208724f325ae89ab1b2f4147e478c36311383860e5312c2d316cc8d08d844351ab362d60be5bf7abad249ea923104994b84b8eb594e30590ce26107270e2fe2f7f56e22fb03c6bf57f4e6a5257fef4e40a3ae3b3885c9c1b6628170988fc1dd3f7417beee4844c780910de27927bc9c00c708e92434f73c805a524e69661e8cbe4c1913ccb41f05dc9346c01d3f3b36d72b3633eafddc1095fcf671d26a0f1d0c79866d8d674fc9f68a01c813a47cd290d942d16d8e6fea57da1c292148943e3bec8c2fbc5d519d5ee9d67c7d2ffc90fc5cf9f8a7595a06c19ef94746ac55c69b0dc09ffe9620e9b339f4acfe0f28f2f3e1b7bb8221fa82709d5c2b4e1dae14f6ed70e098188ffa93ee5dc5ea4eb6282870f84c26e6d3444d74f93a53deac7c6140e87a0459788d4c7c6b6ac5ac26270a7a66ee0397ac174260ca4c703a4af3008d61b284a430dd8c17f18810deb3bd24e778bb4fed686945ee7a0e118bc46d63de813106588b95554f3ac5d1d77ce3e70cfd09f657a50e597c287b3da76d3f6ccc09b86564cfa68bdfb20b84e51f18aeecc86b4bd6d6fc2672cde8c72f8c3a7218aef7c0ec3376b24fe4a72e0c67486ee788149f4fa301bd650c882e282c923c3c46026fd6e01193c636edbb4243ba466f514c24c937f7ef319a1be0da5998643bbaf7e0535f482da66244006306b3f51a356794473c87398cd9b1327e05e647433145bf1101ee3843c3de08fd84d907b1afb37f10aa403f7de9d0597e2a6392a7653670512a16540ad6a14741b3c0288c8e5d4822891892108eacaf676dab33376b92c7e8d220f97d9089b7966e46721b8b604bb5eebfb803580fec1af35fa0e98abaebf66a8501e31a8f1c83d772692a3787b692b2cdb9e1ba7271e310913f9a3940d65afc6d86119a5f52fdb6695670bd81940952c959ceef4dc824011c1e35c2001a5c750f424a16c217c23bc4f71961c4f8efe9da99cb943bfe5e1dc50dc924769fc2d5d0f2fa0f1540ec7483efc26e313ca2a49852a64c24846111b6ae1115f8998cff9dedc28594000804f59ee81cad19a9bdc5be8cde97ebe6a8f9370cf500c7766d69551c43688e773abf1242ffa4dc11538a20532ba1678adf9fbc0908b44fe9c735add2f35204a1b389f847dc35b0730f6d153a67216e8fdd6551f922fb586442c057667d9aa474b6d3d1cc36d9a398e8261a4540c81c34c790ea45487c850d8016a9555c0254118b0482a44487d5112a50a97bf3b90cd6d1c07c77a9e2c381c861a0bdd39a1b5cc276ffd027bdc938c81aaacd44809895e2eb4f4c5018f6116220202f52d93296d4690d87cdfa5c546aa9e8d93b0339ed82772a312321792e8b63c3c6482bc124ff782c14082103f646e4c3a44763844c6f3f13f9c4144ca91b0953d09426f8887b51e39de56b5d103fbf6b595b8383ff82979cc15f4e28d1f6eff21be4e06bd76cf841d5a94517066958c0182ec03dea77658f8614bcc328c448c34deef437bcd51c8630c61f5783db5cba9daa49de841cac63861e5853698772ddf14a1f8fb9003960bc9988d09276b7ff59729d9411a33fda4599fc492380dba3eea852df2c0cca4dc2d812c4facb888c4dc82714ab0401b1c2d40e11e1fcbfd57749da8ff1829e2cea9c6a38065cc1c04a219359f7c54ce9f3e2a7f461915add4c84ade0afef78eb3d325df0e1ef156e57a96b32308187b2fce53da41eb673546176782150bbcf408b0140000c6831206225ffc7fba890804c23a9ad4af77e0927da93afca03f271d9da3982fa4d5d6ccacec515d339c7343f9c6c424624e4b4eaee48789985b3683903f8cc8278b2b73202766876b11ee8fcbaf82fdae914b1673688bd2237f9c99a0af0b409ce2e14626f1b7fca2f81cba756710981cbf91b06228da1dd9507d608ee4c8379806dd788013594f9ae9d149267f5d39b612aae9d0018b360ec2785010f0af6b76cc851c63243661ddc8ca538b2f710b5bf76e0e4c323607bd885d5462d4107939ab4b8aea2ca72139c377c1cb62a17c6dacdaaae0a002639fd845aca85bf1810135b2e5a9599355ac5617b95422d2a78ecb8ec231f59c04e784dead14a2cb2eee0c992365a0655935545be3b6597148277ce6a1ac496403a075001471f28a755f7cfda080ecfeb9ae1a506c44b2fc6cc9c4c699f71b41689d423a559df94ee19bda64b9907729ff8f225190f0a640f00ffee6705b6bb5fe54b7982e5fdf0a08c5b524e4753d58a30b1f44d5a99b8842dab63ca2653c527cf64f0e59afa685ca71116a8aeabee1b0feb165d0253292000dff18e7e34c590dfddf3bcc8c97c517b5766bae7c219157fca0d83feeb9a881d159fd9900625599b3dfa7029b63cf3a393def7861c9634bcb13fd1c0650a03e185a5d61f6f9cbab13a45ff60116cb84dac9e50e60e22b14bcead0e58e2d2a09a56d87cb0f8dd2d36c81877a53c3f2e9d7fcef398f431093539ca445d7fa1924bf2bebfc63c5950e2c09c272c76a2012645eca79ad88ad58af58168ab98b29713c5e9173ef8d9a2856fa7931e0da840a5b609d5c81fb6011ded835f56ada38e50f122965783381dd86c89b3bbaebd9405e5c5720e29b72ab0c3c5fd5137c3e5f9524436f1a0f6a24bfae9fbcef13e5a32a792a670908dace3a66e38aafe0189a4a88f52dd2fac8644c06d5f9d34cd1616b9d0be0307564e4e16bd78bc14619630d326e1c49f6ba25c0cb06b7040ae700475340b62c5b9b4ae8b46099ca0c98ef11ed763c7ba652328ef40f8cefc0c3e456b10ec70f845253349a710b4f9c63fb47bbe74e3d061ca6291a7c89cdcd3cee5717642cf76c6198238c8df1a0dd390d10d81d3c38a02cc1f2d3de75a71742eba97b2691c7fb4120cc44426a42b693d58e769bb96b549b8ff711c2ac77e2a655933d6b6c4eb13f5bf410494db33e7f6e469070aac3e47d2f7ed083bec578e4bdb708bf8930ac403a0d698fd9c3f982bbafe40f8b966dabe74c418a75a12523ae6b36511d36612a55ae3649cd58862ea867a43d74b40279133f46ab7f640402b6b1f1d829129cf27ea68c3577590048067e0184467e2df7ad5d4620a59b73a2112c0d1f766a996e13b95f8e29cd31e592bb0cc07c8f5d50a469ef0bbefb87b89501faaa6f25759959f629aaa39275311d49190d440fb6202206cb97bafd2fb28114884b80db62d6dbb8ac62838c7e06a7c4c673cc40d3205386672c6091b3ea768cd441e745fa225e30fe2d3a57f45499f8a23c738c042d50f94f46139278178aeae93c76210986141d6ab2edd06944c8b6ffc51dffdc8709df292239175a6ef390df32f070446ccc97210be3f7d0ad6b0aae492acd30cdd35b89337f3f23acf900050f36c476b00104cc3127fe8abf5a0eaa9f1d61f6be1bd2b56d363096bc1b6696a838bbc04cb3539bed7848242cd2ee084e42c3d33cc46a9258fa01e3d5efbfff1cbba2460253081ef9a346020d278145dfd9d76a21c1c5f31530c400de1c98150b2075855c7b4ec18ddb4b35203715e8c473b9d7c6995997c196a5db690bf12706d948136e10846dafbbbea81ff9dc87b83d8e9181f74a7dd9a1d40d81e88cf5407182f7059dfbd22f999357eb563a24322bc6afda8075f8d080a199682ed2195cdc9e4949fb048679c0aa257d9df67c83b868f56b905b43844e78e3857d0cd3b4c4b84c4754e85a99da01 \ No newline at end of file diff --git a/src/10/input.test.ts b/src/10/input.test.ts new file mode 100644 index 0000000..7c45c0b --- /dev/null +++ b/src/10/input.test.ts @@ -0,0 +1,14 @@ +import { assertEquals } from "@std/assert"; +import { main } from "./main.ts"; + +const target = "input"; + +Deno.test(`correct trailhead score sum for ${target}`, async () => { + const result = await main(target); + assertEquals(result.trailheadScores, 789); +}); + +Deno.test(`correct trailhead rating sum for ${target}`, async () => { + const result = await main(target); + assertEquals(result.trailheadRatings, 1735); +}); diff --git a/src/10/main.ts b/src/10/main.ts new file mode 100644 index 0000000..ebf58d7 --- /dev/null +++ b/src/10/main.ts @@ -0,0 +1,100 @@ +// { trailheadScores: 789, trailheadRatings: 1735 } +// Elapsed: 5ms + +type Position = [x: number, y: number]; +type Id = number; +type Tile = { + height: number; + reachablePeaks: Id[]; + id: Id; +}; +type TrailMap = Tile[][]; + +function getNeighbors([x, y]: Position): Position[] { + return [ + [x, y - 1], + [x + 1, y], + [x, y + 1], + [x - 1, y], + ]; +} + +function descend( + positions: Position[], + nextHeight: number, + trailMap: TrailMap, +) { + if (nextHeight >= 0) { + const nextPositions = positions.reduce((agg, position) => { + const tile = trailMap[position[1]][position[0]]; + getNeighbors(position).forEach(([x, y]) => { + const neighborTile = trailMap[y]?.[x]; + if (neighborTile && neighborTile.height === nextHeight) { + agg[neighborTile.id] ??= [x, y]; + neighborTile.reachablePeaks.push(...tile.reachablePeaks); + } + }); + return agg; + }, {} as Record); + descend(Object.values(nextPositions), nextHeight - 1, trailMap); + } +} + +export async function main(target = "input") { + const dirpath = new URL(".", import.meta.url).pathname; + const text = await Deno.readTextFile(`${dirpath}${target}.txt`); + + const { trailMap, trailheads, peaks } = text.split("\n").reduce( + (agg, line, idxY) => { + if (line) { + agg.trailMap.push( + line.split("").map((char, idxX) => { + const height = Number(char); + if (height === 0) { + agg.trailheads.push([idxX, idxY]); + } + if (height === 9) { + agg.peaks.push([idxX, idxY]); + return { + height, + reachablePeaks: [agg.nextTileId], + id: agg.nextTileId++, + }; + } + return { + height, + reachablePeaks: [], + id: agg.nextTileId++, + }; + }), + ); + } + return agg; + }, + { + trailMap: [] as TrailMap, + trailheads: [] as Position[], + peaks: [] as Position[], + nextTileId: 0, + }, + ); + + descend(peaks, 8, trailMap); + + return { + trailheadScores: trailheads.reduce( + (agg, [x, y]) => agg + new Set(trailMap[y][x].reachablePeaks).size, + 0, + ), + trailheadRatings: trailheads.reduce( + (agg, [x, y]) => agg + trailMap[y][x].reachablePeaks.length, + 0, + ), + }; +} + +if (import.meta.main) { + const startTime = performance.now(); + console.log(await main()); + console.log(`Elapsed: ${Math.round(performance.now() - startTime)}ms`); +} diff --git a/src/10/sample.txt b/src/10/sample.txt new file mode 100644 index 0000000..cada9b3 --- /dev/null +++ b/src/10/sample.txt @@ -0,0 +1,8 @@ +89010123 +78121874 +87430965 +96549874 +45678903 +32019012 +01329801 +10456732 diff --git a/src/10/test.ts b/src/10/test.ts new file mode 100644 index 0000000..1fbca05 --- /dev/null +++ b/src/10/test.ts @@ -0,0 +1,14 @@ +import { assertEquals } from "@std/assert"; +import { main } from "./main.ts"; + +const target = "sample"; + +Deno.test(`correct trailhead score sum for ${target}`, async () => { + const result = await main(target); + assertEquals(result.trailheadScores, 36); +}); + +Deno.test(`correct trailhead rating sum for ${target}`, async () => { + const result = await main(target); + assertEquals(result.trailheadRatings, 81); +});