diff --git a/2019/day-03/input.txt b/2019/day-03/input.txt new file mode 100644 index 0000000..4c3baef --- /dev/null +++ b/2019/day-03/input.txt @@ -0,0 +1,2 @@ +R1000,D940,L143,D182,L877,D709,L253,U248,L301,U434,R841,U715,R701,U92,R284,U115,R223,U702,R969,U184,L992,U47,L183,U474,L437,D769,L71,U96,R14,U503,R144,U432,R948,U96,L118,D696,R684,U539,L47,D851,L943,U606,L109,D884,R157,U946,R75,U702,L414,U347,R98,D517,L963,D177,R467,D142,L845,U427,R357,D528,L836,D222,L328,U504,R237,U99,L192,D147,L544,D466,R765,U845,L267,D217,L138,U182,R226,U466,R785,U989,R55,D822,L101,U292,R78,U962,R918,U218,L619,D324,L467,U885,L658,U890,L764,D747,R369,D930,L264,D916,L696,U698,R143,U537,L922,U131,R141,D97,L76,D883,R75,D657,R859,U503,R399,U33,L510,D318,L455,U128,R146,D645,L147,D651,L388,D338,L998,U321,L982,U150,R123,U834,R913,D200,L455,D479,L38,U860,L471,U945,L946,D365,L377,U816,R988,D597,R181,D253,R744,U472,L345,U495,L187,D443,R924,D536,R847,U430,L145,D827,L152,D831,L886,D597,R699,D751,R638,D580,L488,D566,L717,D220,L965,D587,L638,D880,L475,D165,L899,U388,R326,D568,R940,U550,R788,D76,L189,D641,R629,D383,L272,D840,L441,D709,L424,U158,L831,D576,R96,D401,R425,U525,L378,D907,L645,U609,L336,D232,L259,D280,L523,U938,R190,D9,L284,U941,L254,D657,R572,U443,L850,U508,L742,D661,L977,U910,L190,U626,R140,U762,L673,U741,R317,D518,R111,U28,R598,D403,R465,D684,R79,U725,L556,U302,L367,U306,R632,D550,R89,D292,R561,D84,L923,D109,L865,D880,L387,D24,R99,U934,L41,U29,L225,D12,L818,U696,R652,U327,L69,D773,L618,U803,L433,D467,R840,D281,R161,D400,R266,D67,L205,D94,R551,U332,R938,D759,L437,D515,L480,U774,L373,U478,R963,D863,L735,U138,L580,U72,L770,U968,L594 +L990,D248,L833,U137,L556,U943,R599,U481,R963,U812,L825,U421,R998,D847,R377,D19,R588,D657,R197,D354,L548,U849,R30,D209,L745,U594,L168,U5,L357,D135,R94,D686,R965,U838,R192,U428,L861,U354,R653,U543,L633,D508,R655,U575,R709,D53,L801,D709,L92,U289,L466,D875,R75,D448,R576,D972,L77,U4,L267,D727,L3,D687,R743,D830,L803,D537,L180,U644,L204,U407,R866,U886,R560,D848,R507,U470,R38,D652,R806,D283,L836,D629,R347,D679,R609,D224,L131,D616,L687,U181,R539,D829,L598,D55,L806,U208,R886,U794,L268,D365,L145,U690,R50,D698,L140,D512,L551,U845,R351,U724,R405,D245,L324,U181,L824,U351,R223,D360,L687,D640,L653,U158,R786,D962,R931,D151,R939,D34,R610,U684,L694,D283,R402,D253,R388,D195,R732,U809,R246,D571,L820,U742,L507,U967,L886,D693,L273,U558,L914,D122,R146,U788,R83,U149,R241,U616,R326,U40,L192,D845,L577,U803,L668,D443,R705,D793,R443,D883,L715,U757,R767,D360,L289,D756,R696,D236,L525,U872,L332,U203,L152,D234,R559,U191,R340,U926,L746,D128,R867,D562,L100,U445,L489,D814,R921,D286,L378,D956,L36,D998,R158,D611,L493,U542,R932,U957,R55,D608,R790,D388,R414,U670,R845,D394,L572,D612,R842,U792,R959,U7,L285,U769,L410,D940,L319,D182,R42,D774,R758,D457,R10,U82,L861,D901,L310,D217,R644,U305,R92,U339,R252,U460,R609,D486,R553,D798,R809,U552,L183,D238,R138,D147,L343,D597,L670,U237,L878,U872,R789,U268,L97,D313,R22,U343,R907,D646,L36,D516,L808,U622,L927,D982,L810,D149,R390,U101,L565,U488,L588,U426,L386,U305,R503,U227,R969,U201,L698,D850,R800,D961,R387,U632,R543,D541,R750,D174,R543,D237,R487,D932,R220 \ No newline at end of file diff --git a/2019/day-03/solution.js b/2019/day-03/solution.js new file mode 100644 index 0000000..e923220 --- /dev/null +++ b/2019/day-03/solution.js @@ -0,0 +1,16 @@ +const fs = require('fs') +const path = require('path') +const filePath = path.join(__dirname, 'input.txt') +const { findWireIntersections, getClosesetIntersection } = require('./wires') +const { linesToArray } = require('../../2018/inputParser') + +fs.readFile(filePath, { encoding: 'utf8' }, (err, data) => { + if (err) throw err + + data = linesToArray(data.trim()) + + const answer = getClosesetIntersection(findWireIntersections(data)).distance + + console.log('-- Part 1 --') + console.log(`Answer: ${answer}`) +}) diff --git a/2019/day-03/wires.js b/2019/day-03/wires.js new file mode 100644 index 0000000..5337af8 --- /dev/null +++ b/2019/day-03/wires.js @@ -0,0 +1,121 @@ +const intersection = require('path-intersection') +const { distance } = require('../../2018/day-06/coordinates') // Manhattan distance function from last year + +const elfWireToSVGPath = (path) => { + const replacements = { + R: 'h', // R(ight) becomes relative positive horizontal lineto + L: 'h-', // L(eft) becomes relative negative horizontal lineto + U: 'v-', // U(p) becomes relative negative vertical line + D: 'v', // D(own) becomes relative positive vertical line + ',': ' ' // Separators are done with whitespace + } + path = path.trim() + + const pattern = new RegExp(Object.keys(replacements).join('|'), 'gi') + path = path.replace(pattern, (match) => { + return replacements[match] + }) + + return `M0,0 ${path}` +} + +const findWireIntersections = (wires) => { + wires = wires.map(elfWireToSVGPath) + const ints = intersection( + ...wires + ).map((point) => { + return { x: parseInt(point.x), y: parseInt(point.y) } + }) + + return ints.sort(isCloser.manhattan) +} + +const isCloser = { + manhattan: (intA, intB) => { + const origin = { x: 0, y: 0 } + intA.distance = distance(origin, intA) + intB.distance = distance(origin, intB) + if (intA.distance < intB.distance) { + return -1 + } + if (intA.distance > intB.distance) { + return 1 + } + if (intA.distance === intB.distance) { + return 0 + } + } +} + +const advance = ({ segment, remaining, distance, current }) => { + // Track the step + switch (direction) { + case 'U': // Up + current.y += -dimension + break + case 'D': // Down + current.y += dimension + break + case 'R': // Right + current.x += dimension + break + case 'L': // Left + current.x += -dimension + } + remaining += -1 + distance++ +} + +const getIntersectionWireDistance = ({ intersection, wires }) => { + intersection.wireDistance = 0 + + wires.reduce((wire) => { + const segments = wire.split(',') + const current = { x: 0, y: 0 } + const distance = 0 + + segments.forEach((segment, idx) => { + const direction = segment.slice(0, 1) + const length = parseInt(segment.slice(1)) + for (let d = 0; d < length; d++) { + advance({ direction, remaining, distance, current }) + // Reached the desired intersection, stop counting and track result + if (current.x === intersection.x && current.y === intersection.y) { + intersection.wireDistance += distance + break + } + } + }) + }, 0) + + return intersection.wireDistance +} + +const getClosesetIntersection = ({ + intersections, + method = 'manhattan' +}) => { + intersections.sort(isCloser[method]) + + // TODO: Remove workaround for bug in SVG intersection library + // https://github.com/bpmn-io/path-intersection/issues/10 + // + // The shared origin inconsistently shows up in the intersection list + if (parseInt(intersections[0].x) === 0 && parseInt(intersections[0].y) === 0) { + // Skip the shared origin since all wires start at origin + return intersections[1] + } + return intersections[0] +} + +const getClosesetIntersectionByWire = (intersections) => { + intersections.sort(isCloserByWire) +} + +module.exports = { + elfWireToSVGPath, + findWireIntersections, + getClosesetIntersection, + getIntersectionWireDistance, + getClosestIntersectionByWireDistance +} diff --git a/2019/day-03/wires.test.js b/2019/day-03/wires.test.js new file mode 100644 index 0000000..dc7ab23 --- /dev/null +++ b/2019/day-03/wires.test.js @@ -0,0 +1,91 @@ +/* eslint-env mocha */ +const expect = require('chai').expect +const { elfWireToSVGPath, findWireIntersections, getClosesetIntersection, getIntersectionWireDistance, getClosesetIntersectionByWire } = require('./wires') + +describe('--- 2019 Day 3: Crossed Wires ---', () => { + describe('Part 1', () => { + describe('elfWiresToSVGPath()', () => { + it('converts elfwire syntax to svg path syntax', () => { + const wire = 'R75,D30,R83,U83,L12,D49,R71,U7,L72' + const expected = 'M0,0 h75 v30 h83 v-83 h-12 v49 h71 v-7 h-72' + const actual = elfWireToSVGPath(wire) + expect(actual).to.equal(expected) + }) + }) + describe('findWireIntersections()', () => { + it('finds the intersection points of multiple wires', () => { + const wires = [ + 'R8,U5,L5,D3', + 'U7,R6,D4,L4' + ] + const expected = [ + { x: 0, y: 0, distance: 0 }, + { x: 3, y: -3, distance: 6 }, + { x: 6, y: -5, distance: 11 } + ] + const actual = findWireIntersections(wires) + expect(actual).to.deep.equal(expected) + }) + }) + describe('getClosestIntersection()', () => { + it('finds the closest intersection in a list of intersections', () => { + const intersections = [ + { x: 23, y: 45 }, + { x: 48, y: -10 }, + { x: 3, y: 3 }, + { x: 3, y: 3 } + ] + const expected = { x: 3, y: 3, distance: 6 } + const actual = getClosesetIntersection(intersections) + expect(actual).to.deep.equal(expected) + }) + }) + it('can be used to find the distance to the closest intersection', () => { + const wiresets = [ + [ + 'R75,D30,R83,U83,L12,D49,R71,U7,L72', + 'U62,R66,U55,R34,D71,R55,D58,R83' + ], [ + 'R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51', + 'U98,R91,D20,R16,D67,R40,U7,R15,U6,R7' + ] + ] + const distances = [159, 135] + wiresets.forEach((wires, idx) => { + expect(getClosesetIntersection(findWireIntersections(wires)).distance).to.equal(distances[idx]) + }) + }) + describe('getIntersectionWireDistance()', () => { + it('calculates the summed total wire length to reach the specified intersection', () => { + const wires = [ + 'R8,U5,L5,D3', + 'U7,R6,D4,L4' + ] + const expected = [ + 40, 30 + ] + const intersections = findWireIntersections(...wires) + const actual = intersections.map((inter) => getIntersectionWireDistance({ wires, intersection: inter })) + expect(actual).to.deep.equal(expected) + }) + }) + describe('getClosestIntersectionByWireDistance()', () => { + it('can be used to find the wire distance to the closest intersection', () => { + const wiresets = [ + [ + 'R75,D30,R83,U83,L12,D49,R71,U7,L72', + 'U62,R66,U55,R34,D71,R55,D58,R83' + ], [ + 'R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51', + 'U98,R91,D20,R16,D67,R40,U7,R15,U6,R7' + ] + ] + const distances = [610, 410] + wiresets.forEach((wires, idx) => { + const intersections = findWireIntersections(wires) + expect(getClosesetIntersectionByWire({ intersections, wires }).distance).to.equal(distances[idx]) + }) + }) + }) + }) +}) diff --git a/index.js b/index.js index 8bc439d..0314d0a 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -require('./2019/day-02/solution') +require('./2019/day-03/solution') diff --git a/package-lock.json b/package-lock.json index 3bc796e..b2be39e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7854,6 +7854,11 @@ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, + "path-intersection": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-intersection/-/path-intersection-2.0.1.tgz", + "integrity": "sha512-tvqxG1oZoq1q7QidC5REg6tafAA38X4VvB5ZLzPAl/nIoh05dmYegTJUwNXOyJ7D3Qk/rN6KsZrbSPAyHx/AiA==" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", diff --git a/package.json b/package.json index dacdf9a..7eee33d 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "index.js", "private": true, "scripts": { + "start": "node index.js", "pretest": "npm run lint", "test": "nyc mocha \"./20*/**/*.test.js\"", "posttest": "nyc report --reporter=html --reporter=text-lcov > coverage.lcov", @@ -35,5 +36,8 @@ "nyc": "^14.1.1", "semantic-release": "^15.13.31", "standard": "^14.3.1" + }, + "dependencies": { + "path-intersection": "^2.0.1" } }
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: