3_1.ts 7.2 KB

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