|
@@ -0,0 +1,102 @@
|
|
|
|
+import { LoadInput } from "../common.ts";
|
|
|
|
+
|
|
|
|
+const tests_EZ = [
|
|
|
|
+ "RL",
|
|
|
|
+ "",
|
|
|
|
+ "AAA = (BBB, CCC)",
|
|
|
|
+ "BBB = (DDD, EEE)",
|
|
|
|
+ "CCC = (ZZZ, GGG)",
|
|
|
|
+ "DDD = (DDD, DDD)",
|
|
|
|
+ "EEE = (EEE, EEE)",
|
|
|
|
+ "GGG = (GGG, GGG)",
|
|
|
|
+ "ZZZ = (ZZZ, ZZZ)",
|
|
|
|
+];
|
|
|
|
+
|
|
|
|
+const tests_longer = [
|
|
|
|
+ "LLR",
|
|
|
|
+ "",
|
|
|
|
+ "AAA = (BBB, BBB)",
|
|
|
|
+ "BBB = (AAA, ZZZ)",
|
|
|
|
+ "ZZZ = (ZZZ, ZZZ)",
|
|
|
|
+];
|
|
|
|
+
|
|
|
|
+const START_NODE: keyof MapNodeTree = "AAA";
|
|
|
|
+const END_NODE: keyof MapNodeTree = "ZZZ";
|
|
|
|
+
|
|
|
|
+const input = await LoadInput(8);
|
|
|
|
+
|
|
|
|
+const [directions, map] = ParseMap(input);
|
|
|
|
+
|
|
|
|
+let nodeID: keyof MapNodeTree|undefined = START_NODE;
|
|
|
|
+let stepsTaken = 0;
|
|
|
|
+
|
|
|
|
+while (nodeID = GetNextNodeInMap(directions, map, nodeID, stepsTaken)) {
|
|
|
|
+ stepsTaken++;
|
|
|
|
+ if (nodeID == END_NODE) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+console.log(`A total of ${stepsTaken} steps were taken`);
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * Parse the input into a string of directions and a list of nodes and their child nodes
|
|
|
|
+ *
|
|
|
|
+ * @param {string[]} map The array of lines representing the map
|
|
|
|
+ * @returns {[string, MapNodeTree]} A string of the directions to take and the node tree for the map
|
|
|
|
+ */
|
|
|
|
+function ParseMap(map: string[]): [string, MapNodeTree] {
|
|
|
|
+ const tree: MapNodeTree = {};
|
|
|
|
+ const directions = map.shift() || "";
|
|
|
|
+ // Discard empty line
|
|
|
|
+ map.shift();
|
|
|
|
+
|
|
|
|
+ let nodeLine:string|undefined = "";
|
|
|
|
+ while(nodeLine = map.shift()) {
|
|
|
|
+ const [id, leftNode, rightNode] = Array.from(nodeLine.matchAll(/(\w{3})/g), (match) => match[1]);
|
|
|
|
+
|
|
|
|
+ if (!id || !leftNode || !rightNode) {
|
|
|
|
+ throw new Error(`Failed to parse line: ${nodeLine}\n\tid: ${id}\n\tleftNode: ${leftNode}\n\trightNode: ${rightNode}`);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ tree[id] = {L: leftNode, R: rightNode};
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return [directions, tree];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * Find the ID of the next node to visit
|
|
|
|
+ *
|
|
|
|
+ * @param {string} directions The entire string of directions
|
|
|
|
+ * @param {MapNodeTree} map A list of nodes and their child nodes
|
|
|
|
+ * @param {keyof MapNodeTree} currentNode The ID of the node we're current at
|
|
|
|
+ * @param {number} stepsTaken The amount of steps already taken in the trek
|
|
|
|
+ * @returns {keyof MapNodeTree|undefined} The next node ID, or undefined if none was found
|
|
|
|
+ */
|
|
|
|
+function GetNextNodeInMap(directions: string, map: MapNodeTree, currentNode: keyof MapNodeTree, stepsTaken: number): keyof MapNodeTree|undefined {
|
|
|
|
+ let nextNodeID: keyof MapNodeTree|undefined = undefined;
|
|
|
|
+
|
|
|
|
+ // Ensure the current node exists
|
|
|
|
+ if (map[currentNode]) {
|
|
|
|
+ // Get the next direction to travel in as a looping index of the directions string
|
|
|
|
+ const nextDirection = directions[stepsTaken % directions.length] as "L"|"R";
|
|
|
|
+ // Store the next node ID
|
|
|
|
+ nextNodeID = map[currentNode][nextDirection];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return nextNodeID;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * An object representing a tree of nodes
|
|
|
|
+ */
|
|
|
|
+type MapNodeTree = {
|
|
|
|
+ /** The ID of the node */
|
|
|
|
+ [NodeID: string]: {
|
|
|
|
+ /** The ID of the node to the left of this one */
|
|
|
|
+ L: string,
|
|
|
|
+ /** The ID of the node to the right of this one */
|
|
|
|
+ R: string,
|
|
|
|
+ },
|
|
|
|
+};
|