3_1.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import { Inspect, LoadInput } from "../common.ts";
  2. /* Directions */
  3. const Directions = {
  4. NORTH: {
  5. X: 0,
  6. Y: -1,
  7. },
  8. SOUTH: {
  9. X: 0,
  10. Y: 1,
  11. },
  12. EAST: {
  13. X: 1,
  14. Y: 0,
  15. },
  16. WEST: {
  17. X: -1,
  18. Y: 0,
  19. },
  20. NORTH_EAST: {
  21. X: 1,
  22. Y: -1,
  23. },
  24. NORTH_WEST: {
  25. X: -1,
  26. Y: -1,
  27. },
  28. SOUTH_EAST: {
  29. X: 1,
  30. Y: 1,
  31. },
  32. SOUTH_WEST: {
  33. X: -1,
  34. Y: 1,
  35. },
  36. }
  37. const tests = [
  38. "467..114..",
  39. "...*......",
  40. "..35..633.",
  41. "......#...",
  42. "617*......",
  43. ".....+.58.",
  44. "..592.....",
  45. "......755.",
  46. "...$.*....",
  47. ".664.598..",
  48. ];
  49. // Build the find
  50. const testMap = BuildSchematicsMap(tests);
  51. // Find all possible parts
  52. let testParts = ParseSchematics(testMap);
  53. // Validate those parts
  54. testParts = ValidateEngineParts(testParts, testMap);
  55. // Get out output
  56. const sumOfPartNumbers = testParts.reduce((accumulator, part) => accumulator += part.PartNumber, 0);
  57. console.log(`The sum of part numbers is: ${sumOfPartNumbers}`);
  58. /**
  59. * Parse the schematics to find all possible Engine Parts
  60. * @param {string[][]} schematicsMap A 2D grid of characters making up a schematic
  61. * @returns {EnginePart[]} An array of potential Engine Parts
  62. */
  63. function ParseSchematics(schematicsMap: string[][]): EnginePart[] {
  64. const engineParts: EnginePart[] = [];
  65. /** The current pointer within the schematics' 2D array */
  66. const schematicsPointer: Vector2 = {
  67. X: 0,
  68. Y: 0,
  69. };
  70. for(; schematicsPointer.Y < schematicsMap.length; schematicsPointer.Y++) {
  71. // Reset the X pointer each line
  72. schematicsPointer.X = 0;
  73. /** A line from the total schematics */
  74. const schematicsLine = schematicsMap[schematicsPointer.Y];
  75. // Create a reusable EnginePart store
  76. let part: EnginePart|null = null;
  77. for(; schematicsPointer.X < schematicsMap[schematicsPointer.Y].length; schematicsPointer.X++) {
  78. const char = schematicsMap[schematicsPointer.Y][schematicsPointer.X];
  79. // Check if the current character is a number
  80. if (/\d/.test(char)) {
  81. // Check if our EnginePart store is initialized
  82. if (part == null) { part = { PartNumber: 0, Coordinates: [] }; }
  83. if (part.PartNumber) {
  84. // Concatenate the number to the existing part of the Part Number
  85. part.PartNumber = Number("" + part.PartNumber + char);
  86. }
  87. else {
  88. // Start the new Part Number
  89. part.PartNumber = Number(char);
  90. }
  91. // Push the coordinates
  92. part.Coordinates.push({
  93. X: schematicsPointer.X,
  94. Y: schematicsPointer.Y,
  95. });
  96. }
  97. // If it's not
  98. else {
  99. // Check if the EnginePart store has a value
  100. if(part != null) {
  101. // If it does, store it and empty it
  102. engineParts.push(part);
  103. part = null;
  104. }
  105. }
  106. }
  107. }
  108. return engineParts;
  109. }
  110. /**
  111. * Validates all previously parsed Engine Parts
  112. *
  113. * An Engine Part is only valid if it's connected, even diagonally, to a
  114. * symbol other than `.`.
  115. *
  116. * @param {EnginePart[]} potentialParts All potential Engine Parts
  117. * @param {string[][]} schematicsMap A 2D grid of characters making up a schematic
  118. * @returns {EnginePart[]} A filtered list of all valid Engine Parts
  119. */
  120. function ValidateEngineParts(potentialParts: EnginePart[], schematicsMap: string[][]): EnginePart[] {
  121. const validParts: EnginePart[] = [];
  122. for (const part of potentialParts) {
  123. for (const point of part.Coordinates) {
  124. if (BordersASymbol(point, schematicsMap)) {
  125. validParts.push(part);
  126. break;
  127. }
  128. }
  129. }
  130. return validParts;
  131. }
  132. /**
  133. * Check if a point on the schematics map borders a symbol
  134. *
  135. * A symbol is any character other than a digit or a period.
  136. *
  137. * @param point A point on the schematics map
  138. * @param schematicsMap A 2D grid of characters making up a schematic
  139. * @returns {boolean} Whether the point borders a symbol or not
  140. */
  141. function BordersASymbol(point: Vector2, schematicsMap: string[][]): boolean {
  142. // Initialize all border chars as NOPs
  143. let northernBorder = ".";
  144. let easternBorder = ".";
  145. let southernBorder = ".";
  146. let westernBorder = ".";
  147. let northEastBorder = ".";
  148. let northWestBorder = ".";
  149. let southEastBorder = ".";
  150. let southWestBorder = ".";
  151. // Check what borders exist
  152. const northExists = !!schematicsMap[point.Y + Directions.NORTH.Y];
  153. const southExists = !!schematicsMap[point.Y + Directions.SOUTH.Y];
  154. const eastExists = !!schematicsMap[point.Y][point.X + Directions.EAST.X];
  155. const westExists = !!schematicsMap[point.Y][point.X + Directions.WEST.X];
  156. // Find the border chars
  157. if(northExists) {
  158. northernBorder = schematicsMap[point.Y + Directions.NORTH.Y][point.X + Directions.NORTH.X];
  159. if(eastExists) {
  160. northEastBorder = schematicsMap[point.Y + Directions.NORTH_EAST.Y][point.X + Directions.NORTH_EAST.X];
  161. }
  162. if(westExists) {
  163. northWestBorder = schematicsMap[point.Y + Directions.NORTH_WEST.Y][point.X + Directions.NORTH_WEST.X];
  164. }
  165. }
  166. if(southExists) {
  167. southernBorder = schematicsMap[point.Y + Directions.SOUTH.Y][point.X + Directions.SOUTH.X];
  168. if(eastExists) {
  169. southEastBorder = schematicsMap[point.Y + Directions.SOUTH_EAST.Y][point.X + Directions.SOUTH_EAST.X];
  170. }
  171. if(westExists) {
  172. southWestBorder = schematicsMap[point.Y + Directions.SOUTH_WEST.Y][point.X + Directions.SOUTH_WEST.X];
  173. }
  174. }
  175. if (eastExists) {
  176. easternBorder = schematicsMap[point.Y + Directions.EAST.Y][point.X + Directions.EAST.X];
  177. }
  178. if (westExists) {
  179. westernBorder = schematicsMap[point.Y + Directions.WEST.Y][point.X + Directions.WEST.X];
  180. }
  181. // Combine all bordering characters into one long string
  182. const allBorders = northernBorder + easternBorder + southernBorder + westernBorder + northEastBorder + northWestBorder + southEastBorder + southWestBorder;
  183. return /[^0-9\.]/.test(allBorders);
  184. }
  185. /**
  186. * Split the schematics into a 2D grid of characters
  187. *
  188. * @param {string[]} schematics The array of lines from the schematics
  189. * @returns {string[][]} A 2D grid of characters
  190. */
  191. function BuildSchematicsMap(schematics: string[]): string[][] {
  192. return schematics.map((schematicsLine) => schematicsLine.split(''));
  193. }
  194. /**
  195. * An engine part
  196. */
  197. type EnginePart = {
  198. PartNumber: number,
  199. Coordinates: Vector2[],
  200. };
  201. /**
  202. * A symbol on the schematics map
  203. */
  204. type SchematicsSymbol = {
  205. Symbol: string,
  206. Coordinates: Vector2,
  207. };
  208. /**
  209. * A position within a 2D grid
  210. */
  211. type Vector2 = {
  212. X: number,
  213. Y: number,
  214. };