|
@@ -0,0 +1,130 @@
|
|
|
|
+import { DeepClone, Inspect, LoadInput } from "../common.ts";
|
|
|
|
+import { BuildSchematicsMap, ParseSchematics, ValidateEngineParts, SchematicsGear as SchematicsGear, EnginePart } from "./3_common.ts";
|
|
|
|
+
|
|
|
|
+const tests = [
|
|
|
|
+ "467..114..",
|
|
|
|
+ "...*......",
|
|
|
|
+ "..35..633.",
|
|
|
|
+ "......#...",
|
|
|
|
+ "617*......",
|
|
|
|
+ ".....+.58.",
|
|
|
|
+ "..592.....",
|
|
|
|
+ "......755.",
|
|
|
|
+ "...$.*....",
|
|
|
|
+ ".664.598..",
|
|
|
|
+];
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * Test case from /u/i_have_no_biscuits
|
|
|
|
+ * @see https://www.reddit.com/r/adventofcode/comments/189q9wv/2023_day_3_another_sample_grid_to_use/
|
|
|
|
+ */
|
|
|
|
+const tests_reddit = [
|
|
|
|
+ "12.......*..",
|
|
|
|
+ "+.........34",
|
|
|
|
+ ".......-12..",
|
|
|
|
+ "..78........",
|
|
|
|
+ "..*....60...",
|
|
|
|
+ "78..........",
|
|
|
|
+ ".......23...",
|
|
|
|
+ "....90*12...",
|
|
|
|
+ "............",
|
|
|
|
+ "2.2......12.",
|
|
|
|
+ ".*.........*",
|
|
|
|
+ "1.1.......56",
|
|
|
|
+];
|
|
|
|
+
|
|
|
|
+const input = await LoadInput(3);
|
|
|
|
+
|
|
|
|
+// Build the find
|
|
|
|
+const schematics = BuildSchematicsMap(input);
|
|
|
|
+// Find all possible parts
|
|
|
|
+let engineParts = ParseSchematics(schematics);
|
|
|
|
+// Validate those parts
|
|
|
|
+engineParts = ValidateEngineParts(engineParts, schematics);
|
|
|
|
+// Find all potential gears
|
|
|
|
+let gears = FindPotentialGears(schematics);
|
|
|
|
+// Validate those gears
|
|
|
|
+gears = ValidateGears(gears, engineParts);
|
|
|
|
+
|
|
|
|
+// Get our output
|
|
|
|
+const sumOfGearRatios = gears.reduce((accumulator, gear) => accumulator += gear.GearRatio, 0);
|
|
|
|
+console.log(`The sum of Gear Ratios is: ${sumOfGearRatios}`);
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * Find all potential gears within a set of schematics
|
|
|
|
+ *
|
|
|
|
+ * A gear is any * symbol. A valid gear is any gear thats adjacent to
|
|
|
|
+ * exactly two part numbers.
|
|
|
|
+ *
|
|
|
|
+ * @param {string[][]} schematicsMap A 2D grid of characters making up a schematic
|
|
|
|
+ * @returns {SchematicsGear[]} A list of potential gears
|
|
|
|
+ */
|
|
|
|
+function FindPotentialGears(schematicsMap: string[][]): SchematicsGear[] {
|
|
|
|
+ const gearsInSchematics: SchematicsGear[] = [];
|
|
|
|
+
|
|
|
|
+ for (let y = 0; y < schematicsMap.length; y++) {
|
|
|
|
+ for (let x = 0; x < schematicsMap[y].length; x++) {
|
|
|
|
+ // Test if the current cell is a gear
|
|
|
|
+ if (/[*]/.test(schematicsMap[y][x])) {
|
|
|
|
+ gearsInSchematics.push({
|
|
|
|
+ Symbol: "*",
|
|
|
|
+ Coordinates: {
|
|
|
|
+ X: x,
|
|
|
|
+ Y: y,
|
|
|
|
+ },
|
|
|
|
+ GearRatio: -1,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return gearsInSchematics;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * Validate a list of gears
|
|
|
|
+ *
|
|
|
|
+ * A valid gear is any gear thats adjacent to exactly two part numbers.
|
|
|
|
+ *
|
|
|
|
+ * @param {SchematicsGear[]} potentialGears A list of potential gears to validate
|
|
|
|
+ * @param {EnginePart[]} engineParts A list of valid Engine Parts from the schematics
|
|
|
|
+ * @returns {SchematicsGear[]} A list of valid gears
|
|
|
|
+ */
|
|
|
|
+function ValidateGears(potentialGears: SchematicsGear[], engineParts: EnginePart[]): SchematicsGear[] {
|
|
|
|
+ const validGears: SchematicsGear[] = [];
|
|
|
|
+
|
|
|
|
+ for (const gear of potentialGears) {
|
|
|
|
+ // Clone the parts list so we can freely modify it
|
|
|
|
+ let partsClone = DeepClone(engineParts) as EnginePart[];
|
|
|
|
+
|
|
|
|
+ // Filter for parts that are on the same Y level as the gear, or 1 above or below the gear
|
|
|
|
+ partsClone = partsClone.filter((part) => {
|
|
|
|
+ return part.Coordinates[0].Y == gear.Coordinates.Y
|
|
|
|
+ || part.Coordinates[0].Y - 1 == gear.Coordinates.Y
|
|
|
|
+ || part.Coordinates[0].Y + 1 == gear.Coordinates.Y;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // If there aren't at least two left, then it's definitely not valid
|
|
|
|
+ if (partsClone.length < 2) { continue; }
|
|
|
|
+
|
|
|
|
+ // Filter for parts that are on the same X-level, or 1 to the right or left of the gear
|
|
|
|
+ partsClone = partsClone.filter((part) => {
|
|
|
|
+ for(const coord of part.Coordinates) {
|
|
|
|
+ if(gear.Coordinates.X == coord.X
|
|
|
|
+ || gear.Coordinates.X + 1 == coord.X
|
|
|
|
+ || gear.Coordinates.X - 1 == coord.X) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // If there are exactly two left, it's valid
|
|
|
|
+ if (partsClone.length == 2 ) {
|
|
|
|
+ gear.GearRatio = partsClone.reduce((accumulator, part) => accumulator *= part.PartNumber, 1);
|
|
|
|
+ validGears.push(gear);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return validGears;
|
|
|
|
+}
|