Add solution day 21
This commit is contained in:
		
							parent
							
								
									80547b5125
								
							
						
					
					
						commit
						881a530b8c
					
				
					 6 changed files with 1191 additions and 0 deletions
				
			
		
							
								
								
									
										5
									
								
								day_21/input.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								day_21/input.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					540A
 | 
				
			||||||
 | 
					582A
 | 
				
			||||||
 | 
					169A
 | 
				
			||||||
 | 
					593A
 | 
				
			||||||
 | 
					579A
 | 
				
			||||||
							
								
								
									
										78
									
								
								day_21/solve2.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								day_21/solve2.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					기본적인 정의를 합니다.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```my-ml-like-sudo-language
 | 
				
			||||||
 | 
					// fg is shortest available pathes function that returns a path from pos to target.
 | 
				
			||||||
 | 
					// type cmd :: enum {
 | 
				
			||||||
 | 
					//   up'^', down'v', right'<', left'>', accept'A'
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					// type path_set = set of list of cmd;
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					이제 헬퍼 함수를 정이합니다.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					// \product is a function that returns a list of all possible combinations of two lists.
 | 
				
			||||||
 | 
					// \product :: path_set -> path_set -> path_set
 | 
				
			||||||
 | 
					// \product [] ys = []
 | 
				
			||||||
 | 
					// \product xs [] = []
 | 
				
			||||||
 | 
					// \product (x:xs) y = (concat x y) ++ (\product xs y)
 | 
				
			||||||
 | 
					// infix 5 \product // associativity is not important. because it commutative.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ++ is union of two sets.
 | 
				
			||||||
 | 
					// concat :: cmd -> path_set -> path_set
 | 
				
			||||||
 | 
					// concat x [] = [x]
 | 
				
			||||||
 | 
					// concat x (y:ys) = (x:y) ++ (concat x ys)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// product :: path_set -> path_set -> path_set
 | 
				
			||||||
 | 
					// product a b = a \product b
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```my-ml-like
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type start_pos = cmd
 | 
				
			||||||
 | 
					type target_pos = cmd
 | 
				
			||||||
 | 
					type next_pos = cmd
 | 
				
			||||||
 | 
					available_paths ::  start_pos -> target_pos -> path_set
 | 
				
			||||||
 | 
					type Handler 'T :: start_pos -> ('T, next_pos)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// define unit, mappend. these are monoid.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// unit :: Handler of path_set
 | 
				
			||||||
 | 
					// unit = s -> t -> (t, s)
 | 
				
			||||||
 | 
					// mappend :: Handler of path_set -> Handler of path_set -> Handler of path_set
 | 
				
			||||||
 | 
					// mappend f g = s -> 
 | 
				
			||||||
 | 
					//  let (t, s') = f s in
 | 
				
			||||||
 | 
					//  let (a ,s'') = g s'
 | 
				
			||||||
 | 
					//  in ( (product t a )  , s'')
 | 
				
			||||||
 | 
					// 
 | 
				
			||||||
 | 
					// move :: Handler of (cmd -> set of list of cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// move s cmds x = available_paths x \product  
 | 
				
			||||||
 | 
					// accept :: Handler of (list of cmd -> list of cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// f = fg 'A'
 | 
				
			||||||
 | 
					// f :: (list of cmd) -> set of (list of cmd)
 | 
				
			||||||
 | 
					// statement execute 'A' (f (x)) = x
 | 
				
			||||||
 | 
					// f x = move x; accept;
 | 
				
			||||||
 | 
					// f x:tails = (move x; accept; f tails) 'A'
 | 
				
			||||||
 | 
					// sl is get shortest path length
 | 
				
			||||||
 | 
					// sl :: list of cmd -> Int
 | 
				
			||||||
 | 
					// sl cmd = (f cmd) |> (foldr (x,y) -> min(x,y), INF)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// operator \product is a function that returns a list of all possible combinations of two lists.
 | 
				
			||||||
 | 
					// \product :: list of cmd -> list of cmd -> list of cmd
 | 
				
			||||||
 | 
					// \product [] ys = ys
 | 
				
			||||||
 | 
					// \product xs [] = xs
 | 
				
			||||||
 | 
					// \product (x:xs) (y:ys) = x \product (y:ys) ++ xs \product (y:ys)
 | 
				
			||||||
 | 
					// infixr 5 \product
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// sl (a \product b) = sl a + sl b
 | 
				
			||||||
 | 
					// f /(.*)A/:rest = f '$1A' \product f rest
 | 
				
			||||||
 | 
					// sl(f /(.*)A/:rest)) = sl (f '$1A' \product f rest)
 | 
				
			||||||
 | 
					//                     = sl (f '$1A') + sl (f rest)
 | 
				
			||||||
 | 
					// 
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										270
									
								
								day_21/solve_1.test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								day_21/solve_1.test.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,270 @@
 | 
				
			||||||
 | 
					import { assertEquals, assertThrows } from "jsr:@std/assert";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    Command,
 | 
				
			||||||
 | 
					    DIRPAD_FIRST_POS,
 | 
				
			||||||
 | 
					    executeCommandsOnDirpadRobot,
 | 
				
			||||||
 | 
					    executeCommandsOnNumpadRobot,
 | 
				
			||||||
 | 
					    getDirpadNumber,
 | 
				
			||||||
 | 
					    getNextDirpadRobotState,
 | 
				
			||||||
 | 
					    getNextNumpadRobotState,
 | 
				
			||||||
 | 
					    getNumpadNumber,
 | 
				
			||||||
 | 
					    getPosFromDirpadNumber,
 | 
				
			||||||
 | 
					    getPosFromNumpadNumber,
 | 
				
			||||||
 | 
					    NUMPAD_FIRST_POS,
 | 
				
			||||||
 | 
					    Pos,
 | 
				
			||||||
 | 
					    shortestPathesOnDirpadRobot,
 | 
				
			||||||
 | 
					    shortestPathesOnNumpadRobot,
 | 
				
			||||||
 | 
					    solve_1,
 | 
				
			||||||
 | 
					} from "./solve_1.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("getNumbericKeypadNumber", () => {
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([0, 0]), "7");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([1, 0]), "8");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([2, 0]), "9");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([0, 1]), "4");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([1, 1]), "5");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([2, 1]), "6");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([0, 2]), "1");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([1, 2]), "2");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([2, 2]), "3");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([0, 3]), " ");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([1, 3]), "0");
 | 
				
			||||||
 | 
					    assertEquals(getNumpadNumber([2, 3]), "A");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("getPosFromNumpadNumber", () => {
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("7"), [0, 0]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("8"), [1, 0]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("9"), [2, 0]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("4"), [0, 1]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("5"), [1, 1]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("6"), [2, 1]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("1"), [0, 2]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("2"), [1, 2]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("3"), [2, 2]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("0"), [1, 3]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromNumpadNumber("A"), [2, 3]);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("getNextKeypadRobotState", () => {
 | 
				
			||||||
 | 
					    assertThrows(() => getNextNumpadRobotState({ pos: [0, 0] }, "^"));
 | 
				
			||||||
 | 
					    assertEquals(getNextNumpadRobotState({ pos: [0, 0] }, "v"), {
 | 
				
			||||||
 | 
					        pos: [0, 1],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertThrows(() => getNextNumpadRobotState({ pos: [0, 0] }, "<"));
 | 
				
			||||||
 | 
					    assertEquals(getNextNumpadRobotState({ pos: [0, 0] }, ">"), {
 | 
				
			||||||
 | 
					        pos: [1, 0],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertEquals(getNextNumpadRobotState({ pos: [0, 0] }, "A"), {
 | 
				
			||||||
 | 
					        pos: [0, 0],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assertEquals(getNextNumpadRobotState({ pos: [1, 1] }, "^"), {
 | 
				
			||||||
 | 
					        pos: [1, 0],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertEquals(getNextNumpadRobotState({ pos: [1, 1] }, "v"), {
 | 
				
			||||||
 | 
					        pos: [1, 2],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertEquals(getNextNumpadRobotState({ pos: [1, 1] }, "<"), {
 | 
				
			||||||
 | 
					        pos: [0, 1],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertEquals(getNextNumpadRobotState({ pos: [1, 1] }, ">"), {
 | 
				
			||||||
 | 
					        pos: [2, 1],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertEquals(getNextNumpadRobotState({ pos: [1, 1] }, "A"), {
 | 
				
			||||||
 | 
					        pos: [1, 1],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("getDirKeypadNumber", () => {
 | 
				
			||||||
 | 
					    assertEquals(getDirpadNumber([0, 0]), " ");
 | 
				
			||||||
 | 
					    assertEquals(getDirpadNumber([1, 0]), "^");
 | 
				
			||||||
 | 
					    assertEquals(getDirpadNumber([2, 0]), "A");
 | 
				
			||||||
 | 
					    assertEquals(getDirpadNumber([0, 1]), "<");
 | 
				
			||||||
 | 
					    assertEquals(getDirpadNumber([1, 1]), "v");
 | 
				
			||||||
 | 
					    assertEquals(getDirpadNumber([2, 1]), ">");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("getPosFromDirKeypadNumber", () => {
 | 
				
			||||||
 | 
					    assertEquals(getPosFromDirpadNumber(" "), [0, 0]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromDirpadNumber("^"), [1, 0]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromDirpadNumber("A"), [2, 0]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromDirpadNumber("<"), [0, 1]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromDirpadNumber("v"), [1, 1]);
 | 
				
			||||||
 | 
					    assertEquals(getPosFromDirpadNumber(">"), [2, 1]);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("getNextDirKeypadRobotState", () => {
 | 
				
			||||||
 | 
					    assertEquals(getNextDirpadRobotState({ pos: [1, 1] }, "^"), {
 | 
				
			||||||
 | 
					        pos: [1, 0],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertThrows(() => getNextDirpadRobotState({ pos: [1, 1] }, "v"));
 | 
				
			||||||
 | 
					    assertEquals(getNextDirpadRobotState({ pos: [1, 1] }, "<"), {
 | 
				
			||||||
 | 
					        pos: [0, 1],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertEquals(getNextDirpadRobotState({ pos: [1, 1] }, ">"), {
 | 
				
			||||||
 | 
					        pos: [2, 1],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertEquals(getNextDirpadRobotState({ pos: [1, 1] }, "A"), {
 | 
				
			||||||
 | 
					        pos: [1, 1],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("executeCommandsOnNumpadRobot", () => {
 | 
				
			||||||
 | 
					    const commands = [
 | 
				
			||||||
 | 
					        "<A^A>^^AvvvA".split("") as Command[],
 | 
				
			||||||
 | 
					        "<A^A^>^AvvvA".split("") as Command[],
 | 
				
			||||||
 | 
					        "<A^A^^>AvvvA".split("") as Command[],
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    for (const command of commands) {
 | 
				
			||||||
 | 
					        const out: string[] = [];
 | 
				
			||||||
 | 
					        executeCommandsOnNumpadRobot(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                pos: NUMPAD_FIRST_POS,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            command,
 | 
				
			||||||
 | 
					            (o) => {
 | 
				
			||||||
 | 
					                out.push(o);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        assertEquals(out, ["0", "2", "9", "A"]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("executeCommandsOnDirKeypadRobot", () => {
 | 
				
			||||||
 | 
					    const input_result = [
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "<vA<AA>>^AvAA<^A>A<v<A>>^AvA^A<vA>^A<v<A>^A>AAvA^A<v<A>A>^AAAvA<^A>A"
 | 
				
			||||||
 | 
					                .split("") as Command[],
 | 
				
			||||||
 | 
					            "v<<A>>^A<A>AvA<^AA>A<vAAA>^A",
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        ["v<<A>>^A<A>AvA<^AA>A<vAAA>^A".split("") as Command[], "<A^A>^^AvvvA"],
 | 
				
			||||||
 | 
					    ] as const;
 | 
				
			||||||
 | 
					    for (const [input, result] of input_result) {
 | 
				
			||||||
 | 
					        const out: string[] = [];
 | 
				
			||||||
 | 
					        executeCommandsOnDirpadRobot(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                pos: DIRPAD_FIRST_POS,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            input,
 | 
				
			||||||
 | 
					            (o) => {
 | 
				
			||||||
 | 
					                out.push(o);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        assertEquals(out.join(""), result);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("shortestPathesOnNumpadRobot", () => {
 | 
				
			||||||
 | 
					    const testCase: {
 | 
				
			||||||
 | 
					        pos: Pos;
 | 
				
			||||||
 | 
					        target: string;
 | 
				
			||||||
 | 
					        expect: string[][];
 | 
				
			||||||
 | 
					        msg: string;
 | 
				
			||||||
 | 
					    }[] = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: NUMPAD_FIRST_POS,
 | 
				
			||||||
 | 
					            target: "A",
 | 
				
			||||||
 | 
					            expect: [[]],
 | 
				
			||||||
 | 
					            msg: "should return empty array when target is same as pos",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: [0, 0],
 | 
				
			||||||
 | 
					            target: "7",
 | 
				
			||||||
 | 
					            expect: [[]],
 | 
				
			||||||
 | 
					            msg: "should return empty array when target is same as pos",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: NUMPAD_FIRST_POS,
 | 
				
			||||||
 | 
					            target: "9",
 | 
				
			||||||
 | 
					            expect: [["^", "^", "^"]],
 | 
				
			||||||
 | 
					            msg: "should return shortest path to target",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: [0, 0],
 | 
				
			||||||
 | 
					            target: "5",
 | 
				
			||||||
 | 
					            expect: [["v", ">"], [">", "v"]],
 | 
				
			||||||
 | 
					            msg: "should return shortest path to target",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: [1, 3],
 | 
				
			||||||
 | 
					            target: "1",
 | 
				
			||||||
 | 
					            expect: [["^", "<"]],
 | 
				
			||||||
 | 
					            msg: "should return shortest path to target",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    for (const { pos, target, expect, msg } of testCase) {
 | 
				
			||||||
 | 
					        assertEquals(shortestPathesOnNumpadRobot({ pos }, target), expect, msg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("shortestPathesOnDirpadRobot", () => {
 | 
				
			||||||
 | 
					    const testCase: {
 | 
				
			||||||
 | 
					        pos: Pos;
 | 
				
			||||||
 | 
					        target: string;
 | 
				
			||||||
 | 
					        expect: string[][];
 | 
				
			||||||
 | 
					        msg: string;
 | 
				
			||||||
 | 
					    }[] = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: DIRPAD_FIRST_POS,
 | 
				
			||||||
 | 
					            target: "A",
 | 
				
			||||||
 | 
					            expect: [["A"]],
 | 
				
			||||||
 | 
					            msg: "should return empty array when target is same as pos",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: [1, 0],
 | 
				
			||||||
 | 
					            target: "^",
 | 
				
			||||||
 | 
					            expect: [["A"]],
 | 
				
			||||||
 | 
					            msg: "should return empty array when target is same as pos",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: DIRPAD_FIRST_POS,
 | 
				
			||||||
 | 
					            target: ">",
 | 
				
			||||||
 | 
					            expect: [["v", "A"]],
 | 
				
			||||||
 | 
					            msg: "should return shortest path to target",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: [1, 0],
 | 
				
			||||||
 | 
					            target: ">",
 | 
				
			||||||
 | 
					            expect: [["v", ">", "A"], [">", "v", "A"]],
 | 
				
			||||||
 | 
					            msg: "should return shortest path to target",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pos: [0, 1],
 | 
				
			||||||
 | 
					            target: "^",
 | 
				
			||||||
 | 
					            expect: [[">", "^", "A"]],
 | 
				
			||||||
 | 
					            msg: "should return shortest path to target",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    for (const { pos, target, expect, msg } of testCase) {
 | 
				
			||||||
 | 
					        assertEquals(shortestPathesOnDirpadRobot({ pos }, target), expect, msg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("example test", () => {
 | 
				
			||||||
 | 
					    const testCase: [string, string][] = [
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "029A",
 | 
				
			||||||
 | 
					            "<vA<AA>>^AvAA<^A>A<v<A>>^AvA^A<vA>^A<v<A>^A>AAvA^A<v<A>A>^AAAvA<^A>A",
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "980A",
 | 
				
			||||||
 | 
					            "<v<A>>^AAAvA^A<vA<AA>>^AvAA<^A>A<v<A>A>^AAAvA<^A>A<vA>^A<A>A",
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "179A",
 | 
				
			||||||
 | 
					            "<v<A>>^A<vA<A>>^AAvAA<^A>A<v<A>>^AAvA^A<vA>^AA<A>A<v<A>A>^AAAvA<^A>A",
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "456A",
 | 
				
			||||||
 | 
					            "<v<A>>^AA<vA<A>>^AAvAA<^A>A<vA>^A<A>A<vA>^A<A>A<v<A>A>^AAvA<^A>A",
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "379A",
 | 
				
			||||||
 | 
					            "<v<A>>^AvA^A<vA<AA>>^AAvA<^A>AAvA^A<vA>^AA<A>A<v<A>A>^AAAvA<^A>A",
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    for (const [input, expect] of testCase) {
 | 
				
			||||||
 | 
					        assertEquals(solve_1(input), expect.length);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										254
									
								
								day_21/solve_1.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								day_21/solve_1.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,254 @@
 | 
				
			||||||
 | 
					export type Pos = [number, number];
 | 
				
			||||||
 | 
					export type NumpadRobotState = {
 | 
				
			||||||
 | 
					    pos: Pos;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export type Command = "^" | "v" | "<" | ">" | "A";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getNextPos(pos: Pos, command: Command): Pos {
 | 
				
			||||||
 | 
					    const [x, y] = pos;
 | 
				
			||||||
 | 
					    switch (command) {
 | 
				
			||||||
 | 
					        case "^":
 | 
				
			||||||
 | 
					            return [x, y - 1];
 | 
				
			||||||
 | 
					        case "v":
 | 
				
			||||||
 | 
					            return [x, y + 1];
 | 
				
			||||||
 | 
					        case "<":
 | 
				
			||||||
 | 
					            return [x - 1, y];
 | 
				
			||||||
 | 
					        case ">":
 | 
				
			||||||
 | 
					            return [x + 1, y];
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return pos;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NumbericKeypad = "789\n456\n123\n 0A";
 | 
				
			||||||
 | 
					export const NUMPAD_FIRST_POS: Pos = [2, 3];
 | 
				
			||||||
 | 
					export function getNumpadNumber([x, y]: Pos): string {
 | 
				
			||||||
 | 
					    return NumbericKeypad[y * 4 + x];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export function getPosFromNumpadNumber(num: string): Pos {
 | 
				
			||||||
 | 
					    const idx = NumbericKeypad.indexOf(num);
 | 
				
			||||||
 | 
					    return [idx % 4, Math.floor(idx / 4)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getNextNumpadRobotState(
 | 
				
			||||||
 | 
					    state: NumpadRobotState,
 | 
				
			||||||
 | 
					    command: Command,
 | 
				
			||||||
 | 
					): NumpadRobotState {
 | 
				
			||||||
 | 
					    const nextPos = getNextPos(state.pos, command);
 | 
				
			||||||
 | 
					    const [nx, ny] = nextPos;
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        nx < 0 || nx > 2 || ny < 0 || ny > 3 ||
 | 
				
			||||||
 | 
					        getNumpadNumber(nextPos) === " "
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					            `Invalid command: ${command} for pos: ${state.pos.join(",")} -> ${
 | 
				
			||||||
 | 
					                nextPos.join(",")
 | 
				
			||||||
 | 
					            } is out of bound`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return { pos: nextPos };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DirectionalKeypad = " ^A\n<v>";
 | 
				
			||||||
 | 
					export const DIRPAD_FIRST_POS: Pos = [2, 0];
 | 
				
			||||||
 | 
					export type DirKeypadRobotState = {
 | 
				
			||||||
 | 
					    pos: Pos;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export function getDirpadNumber([x, y]: Pos): string {
 | 
				
			||||||
 | 
					    return DirectionalKeypad[y * 4 + x];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export function getPosFromDirpadNumber(num: string): Pos {
 | 
				
			||||||
 | 
					    const idx = DirectionalKeypad.indexOf(num);
 | 
				
			||||||
 | 
					    return [idx % 4, Math.floor(idx / 4)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getNextDirpadRobotState(
 | 
				
			||||||
 | 
					    state: DirKeypadRobotState,
 | 
				
			||||||
 | 
					    command: Command,
 | 
				
			||||||
 | 
					): DirKeypadRobotState {
 | 
				
			||||||
 | 
					    const nextPos = getNextPos(state.pos, command);
 | 
				
			||||||
 | 
					    const [nx, ny] = nextPos;
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        nx < 0 || nx > 2 || ny < 0 || ny > 1 ||
 | 
				
			||||||
 | 
					        getDirpadNumber(nextPos) === " "
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					            `Invalid command: ${command} for pos: ${state.pos.join(",")} -> ${
 | 
				
			||||||
 | 
					                nextPos.join(",")
 | 
				
			||||||
 | 
					            } is out of bound`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return { pos: nextPos };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function executeCommandsOnNumpadRobot(
 | 
				
			||||||
 | 
					    state: NumpadRobotState,
 | 
				
			||||||
 | 
					    commands: Command[],
 | 
				
			||||||
 | 
					    output: (out: string) => void,
 | 
				
			||||||
 | 
					): NumpadRobotState {
 | 
				
			||||||
 | 
					    return commands.reduce((state, command) => {
 | 
				
			||||||
 | 
					        if (command === "A") {
 | 
				
			||||||
 | 
					            output(getNumpadNumber(state.pos));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return getNextNumpadRobotState(state, command);
 | 
				
			||||||
 | 
					    }, state);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function executeCommandsOnDirpadRobot(
 | 
				
			||||||
 | 
					    state: DirKeypadRobotState,
 | 
				
			||||||
 | 
					    commands: Command[],
 | 
				
			||||||
 | 
					    output: (out: string) => void,
 | 
				
			||||||
 | 
					): DirKeypadRobotState {
 | 
				
			||||||
 | 
					    return commands.reduce((state, command) => {
 | 
				
			||||||
 | 
					        if (command === "A") {
 | 
				
			||||||
 | 
					            output(getDirpadNumber(state.pos));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return getNextDirpadRobotState(state, command);
 | 
				
			||||||
 | 
					    }, state);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function shortestPathesOnNumpadRobot(
 | 
				
			||||||
 | 
					    state: NumpadRobotState,
 | 
				
			||||||
 | 
					    targetNum: string,
 | 
				
			||||||
 | 
					): Command[][] {
 | 
				
			||||||
 | 
					    const targetPos = getPosFromNumpadNumber(targetNum);
 | 
				
			||||||
 | 
					    const len = Math.abs(targetPos[0] - state.pos[0]) +
 | 
				
			||||||
 | 
					        Math.abs(targetPos[1] - state.pos[1]);
 | 
				
			||||||
 | 
					    const queue: [Command[], NumpadRobotState][] = [[[], state]];
 | 
				
			||||||
 | 
					    const paths: Command[][] = [];
 | 
				
			||||||
 | 
					    while (queue.length > 0) {
 | 
				
			||||||
 | 
					        const [path, state] = queue.shift()!;
 | 
				
			||||||
 | 
					        if (path.length === len) {
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                state.pos[0] === targetPos[0] && state.pos[1] === targetPos[1]
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                paths.push(path);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const command of ["^", "v", "<", ">"] as Command[]) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const nextState = getNextNumpadRobotState(state, command);
 | 
				
			||||||
 | 
					                queue.push([path.concat(command), nextState]);
 | 
				
			||||||
 | 
					            } catch (e) {
 | 
				
			||||||
 | 
					                // Ignore invalid command
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return paths;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function shortestPathesOnDirpadRobot(
 | 
				
			||||||
 | 
					    state: DirKeypadRobotState,
 | 
				
			||||||
 | 
					    targetNum: string,
 | 
				
			||||||
 | 
					): Command[][] {
 | 
				
			||||||
 | 
					    const targetPos = getPosFromDirpadNumber(targetNum);
 | 
				
			||||||
 | 
					    const len = Math.abs(targetPos[0] - state.pos[0]) +
 | 
				
			||||||
 | 
					        Math.abs(targetPos[1] - state.pos[1]);
 | 
				
			||||||
 | 
					    const queue: [Command[], DirKeypadRobotState][] = [[[], state]];
 | 
				
			||||||
 | 
					    const paths: Command[][] = [];
 | 
				
			||||||
 | 
					    while (queue.length > 0) {
 | 
				
			||||||
 | 
					        const [path, state] = queue.shift()!;
 | 
				
			||||||
 | 
					        if (path.length === len) {
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                state.pos[0] === targetPos[0] && state.pos[1] === targetPos[1]
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                paths.push(path);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const command of ["^", "v", "<", ">"] as Command[]) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const nextState = getNextDirpadRobotState(state, command);
 | 
				
			||||||
 | 
					                queue.push([path.concat(command), nextState]);
 | 
				
			||||||
 | 
					            } catch (e) {
 | 
				
			||||||
 | 
					                // Ignore invalid command
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return paths.map((p) => p.concat("A"));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function StepShortestPathesOnDirpadRobot(
 | 
				
			||||||
 | 
					    state: DirKeypadRobotState,
 | 
				
			||||||
 | 
					    targetNums: string[],
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    return targetNums.reduce( (acc, targetNum) => {
 | 
				
			||||||
 | 
					        const prevPos = acc.prevPos;
 | 
				
			||||||
 | 
					        const paths = shortestPathesOnDirpadRobot({ pos: prevPos }, targetNum);
 | 
				
			||||||
 | 
					        if (paths.length === 0) {
 | 
				
			||||||
 | 
					            throw new Error(`No path found for ${targetNum} from ${prevPos}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            prevPos: getPosFromDirpadNumber(targetNum),
 | 
				
			||||||
 | 
					            // product of all
 | 
				
			||||||
 | 
					            paths: acc.paths.flatMap((p) => paths.map((np) => p.concat(np))),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        prevPos: state.pos,
 | 
				
			||||||
 | 
					        paths: [[]] as Command[][],
 | 
				
			||||||
 | 
					    }).paths;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function filterShortestPathes(paths: Command[][]) {
 | 
				
			||||||
 | 
					    let len = Number.MAX_SAFE_INTEGER;
 | 
				
			||||||
 | 
					    for (const path of paths) {
 | 
				
			||||||
 | 
					        len = Math.min(len, path.length);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // len = Math.min(...paths.map((p) => p.length)); // this is call stack overflow...
 | 
				
			||||||
 | 
					    // because the array is too big, the argument list is too long.
 | 
				
			||||||
 | 
					    // so we need to calculate the min value by ourself.
 | 
				
			||||||
 | 
					    return paths.filter((p) => p.length === len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function solve_1(code: string) {
 | 
				
			||||||
 | 
					    const numbers = code.split("");
 | 
				
			||||||
 | 
					    let start = NUMPAD_FIRST_POS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const finalOuts: string[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const num of numbers) {
 | 
				
			||||||
 | 
					        console.log("num", num);
 | 
				
			||||||
 | 
					        const numpadPaths = shortestPathesOnNumpadRobot({ pos: start }, num).map((p) => p.concat("A"));
 | 
				
			||||||
 | 
					        if (numpadPaths.length === 0) {
 | 
				
			||||||
 | 
					            throw new Error(`No path found for ${num} from ${start}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        console.log("avail path", numpadPaths.map((p) => p.join("")));
 | 
				
			||||||
 | 
					        const pathes1 = filterShortestPathes(numpadPaths.flatMap((numpadPath) => {
 | 
				
			||||||
 | 
					            return StepShortestPathesOnDirpadRobot({ pos: DIRPAD_FIRST_POS }, numpadPath);
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log("path1",pathes1.map((p) => p.join("")));
 | 
				
			||||||
 | 
					        const pathes2 = filterShortestPathes(pathes1.flatMap((path) => {
 | 
				
			||||||
 | 
					            return StepShortestPathesOnDirpadRobot({ pos: DIRPAD_FIRST_POS }, path);
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					        console.log("path2",pathes2.map((p) => p.join("")));
 | 
				
			||||||
 | 
					        const pathes3 = filterShortestPathes(pathes2.flatMap((path) => {
 | 
				
			||||||
 | 
					            return StepShortestPathesOnDirpadRobot({ pos: DIRPAD_FIRST_POS }, path);
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					        // console.log("path3",pathes3.map((p) => p.join("")));
 | 
				
			||||||
 | 
					        const executePath3 = (path: Command[]) => {
 | 
				
			||||||
 | 
					            const outs: string[] = [];
 | 
				
			||||||
 | 
					            executeCommandsOnDirpadRobot({ pos: DIRPAD_FIRST_POS }, path, (o) => { outs.push(o); });
 | 
				
			||||||
 | 
					            console.log("outs", outs.join(""));
 | 
				
			||||||
 | 
					            return outs;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        finalOuts.push(...executePath3(pathes3[0]));
 | 
				
			||||||
 | 
					        start = getPosFromNumpadNumber(num);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    console.log("finalOuts", finalOuts.join(""), finalOuts.length);
 | 
				
			||||||
 | 
					    return finalOuts.length;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (import.meta.main) {
 | 
				
			||||||
 | 
					    const codes = Deno.readTextFileSync("input.txt").replaceAll("\r","").split("\n");
 | 
				
			||||||
 | 
					    let total = 0;
 | 
				
			||||||
 | 
					    for (const code of codes){
 | 
				
			||||||
 | 
					        const len = solve_1(code);
 | 
				
			||||||
 | 
					        const n = parseInt(code.slice(0,3));
 | 
				
			||||||
 | 
					        console.log("len", len,  n, len * n);
 | 
				
			||||||
 | 
					        total += n * len;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    console.log("total", total);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										124
									
								
								day_21/solve_2.test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								day_21/solve_2.test.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,124 @@
 | 
				
			||||||
 | 
					import { assertEquals } from "jsr:@std/assert/equals";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    availableShortestPaths,
 | 
				
			||||||
 | 
					    Command,
 | 
				
			||||||
 | 
					    inputCommand,
 | 
				
			||||||
 | 
					    inputCommands,
 | 
				
			||||||
 | 
					    Path,
 | 
				
			||||||
 | 
					    PathSetHandler,
 | 
				
			||||||
 | 
					    shortestPathLength,
 | 
				
			||||||
 | 
					} from "./solve_2.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("availableShortestPaths - blocked", () => {
 | 
				
			||||||
 | 
					    const paths = availableShortestPaths("<", "^");
 | 
				
			||||||
 | 
					    assertEquals(paths.map((x) => x.toString()), [">^"]);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("availableShortestPaths - common", () => {
 | 
				
			||||||
 | 
					    const paths = availableShortestPaths(">", "^");
 | 
				
			||||||
 | 
					    assertEquals(paths.map((x) => x.toString()).sort(), ["<^", "^<"].sort());
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("Path - length", () => {
 | 
				
			||||||
 | 
					    const path = new Path(["^", "v", "<", ">", "A"]);
 | 
				
			||||||
 | 
					    assertEquals(path.length, 5);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("Path - execute", () => {
 | 
				
			||||||
 | 
					    const path = new Path(["<", "v", "A", ">", "A"]);
 | 
				
			||||||
 | 
					    const ret: Command[] = [];
 | 
				
			||||||
 | 
					    path.execute("A", (c) => {
 | 
				
			||||||
 | 
					        ret.push(c);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    assertEquals(ret.join(""), "v>");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("Path - concat", () => {
 | 
				
			||||||
 | 
					    const path = new Path(["<", "v", "A", ">", "A"]);
 | 
				
			||||||
 | 
					    assertEquals(path.concat("^").toString(), "<vA>A^");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("PathHandler - coproduct", () => {
 | 
				
			||||||
 | 
					    const lhs = PathSetHandler.fromPathSet([Path.from(["A"])]);
 | 
				
			||||||
 | 
					    const rhs = PathSetHandler.fromPathSet([Path.from(["v"])]);
 | 
				
			||||||
 | 
					    const set = lhs.coproduct(rhs);
 | 
				
			||||||
 | 
					    const [m, _] = set.call(">");
 | 
				
			||||||
 | 
					    assertEquals(
 | 
				
			||||||
 | 
					        m.map((x) => x.toString()).sort(),
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "A",
 | 
				
			||||||
 | 
					            "v",
 | 
				
			||||||
 | 
					        ].sort(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					Deno.test("PathHandler - product", () => {
 | 
				
			||||||
 | 
					    const lhs = PathSetHandler.fromPathSet([Path.from(["A"])]);
 | 
				
			||||||
 | 
					    const rhs = PathSetHandler.fromPathSet([Path.from(["v"])]);
 | 
				
			||||||
 | 
					    const set = lhs.product(rhs);
 | 
				
			||||||
 | 
					    const [m, _] = set.call(">");
 | 
				
			||||||
 | 
					    assertEquals(
 | 
				
			||||||
 | 
					        m.map((x) => x.toString()).sort(),
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "Av",
 | 
				
			||||||
 | 
					        ].sort(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("inputCommand", () => {
 | 
				
			||||||
 | 
					    const handler = inputCommand("^");
 | 
				
			||||||
 | 
					    const [m, a] = handler.call(">");
 | 
				
			||||||
 | 
					    assertEquals(
 | 
				
			||||||
 | 
					        m.map((x) => x.toString()).sort(),
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "<^A",
 | 
				
			||||||
 | 
					            "^<A",
 | 
				
			||||||
 | 
					        ].sort(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("inputCommands", () => {
 | 
				
			||||||
 | 
					    const handler = inputCommands(Path.from([">", "A"]));
 | 
				
			||||||
 | 
					    const [m, a] = handler.call(">");
 | 
				
			||||||
 | 
					    assertEquals(
 | 
				
			||||||
 | 
					        m.map((x) => x.toString()).sort(),
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "A^A",
 | 
				
			||||||
 | 
					        ].sort(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("shortestPathLength", () => {
 | 
				
			||||||
 | 
					    const inputcmdsShort = (start: Command,
 | 
				
			||||||
 | 
					        cmds: Command[]) => shortestPathLength(inputCommands(Path.from(cmds)))(start)[0];
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    assertEquals(inputcmdsShort("A", (["A"])), 1);
 | 
				
			||||||
 | 
					    assertEquals(inputcmdsShort("A", (["A", ">"])), 3);
 | 
				
			||||||
 | 
					    assertEquals(inputcmdsShort("A", (["A", ">", "^"])), 6);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assertEquals(inputcmdsShort("A", (["<"])), 4);
 | 
				
			||||||
 | 
					    assertEquals(inputcmdsShort("A", (["<", "A"])), 8);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deno.test("shortestPathLength - property", () => {
 | 
				
			||||||
 | 
					    // random path list
 | 
				
			||||||
 | 
					    const pathes = [
 | 
				
			||||||
 | 
					        Path.from(["^", "v", "<", ">", "A"]),
 | 
				
			||||||
 | 
					        Path.from(["^", "v", "<", ">", "A", "v"]),
 | 
				
			||||||
 | 
					        Path.from(["^", "v", "<", ">", "A", "v", "<"]),
 | 
				
			||||||
 | 
					        Path.from(["^", "v", "<", ">", "A", "v", "<", ">"]),
 | 
				
			||||||
 | 
					        Path.from(["A", "v", "v", ">", ">", "v", ">", "v"]),
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    for (const paths of pathes) {
 | 
				
			||||||
 | 
					        for (const s of ["^", "v", "<", ">", "A"] as Command[]) {
 | 
				
			||||||
 | 
					            const [x, ...xs] = paths;
 | 
				
			||||||
 | 
					            const s1 = shortestPathLength(inputCommands(Path.from([x])))(s)[0];
 | 
				
			||||||
 | 
					            const s2 = shortestPathLength(inputCommands(Path.from(xs)))(x)[0];
 | 
				
			||||||
 | 
					            const s3 = shortestPathLength(inputCommands(paths))(s)[0];
 | 
				
			||||||
 | 
					            assertEquals(
 | 
				
			||||||
 | 
					                s1 + s2,
 | 
				
			||||||
 | 
					                s3,
 | 
				
			||||||
 | 
					                `s: ${s}, x: ${x}, xs: ${xs}, s1: ${s1}, s2: ${s2}, s3: ${s3}`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										460
									
								
								day_21/solve_2.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										460
									
								
								day_21/solve_2.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,460 @@
 | 
				
			||||||
 | 
					// fg is shortest available pathes function that returns a path from pos to target.
 | 
				
			||||||
 | 
					// type cmd :: enum {
 | 
				
			||||||
 | 
					//   up'^', down'v', right'<', left'>', accept'A'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { getPosFromNumpadNumber, NUMPAD_FIRST_POS, shortestPathesOnNumpadRobot } from "./solve_1.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					export type Command = "^" | "v" | "<" | ">" | "A";
 | 
				
			||||||
 | 
					export type Pos = [number, number];
 | 
				
			||||||
 | 
					// type path = list of cmd;
 | 
				
			||||||
 | 
					// type path_set = set of path;
 | 
				
			||||||
 | 
					export type PathSet = Path[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// |--|--|--|
 | 
				
			||||||
 | 
					// |  |^ | A|
 | 
				
			||||||
 | 
					// |--|--|--|
 | 
				
			||||||
 | 
					// |< |v | >|
 | 
				
			||||||
 | 
					// |--|--|--|
 | 
				
			||||||
 | 
					const lookupCommandPos: Record<Command, Pos> = {
 | 
				
			||||||
 | 
					    "^": [1, 0],
 | 
				
			||||||
 | 
					    "v": [1, 1],
 | 
				
			||||||
 | 
					    "<": [0, 1],
 | 
				
			||||||
 | 
					    ">": [2, 1],
 | 
				
			||||||
 | 
					    "A": [2, 0],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export function commandToPos(command: Command): Pos {
 | 
				
			||||||
 | 
					    return lookupCommandPos[command];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export function posToCommand(pos: Pos): Command {
 | 
				
			||||||
 | 
					    for (const [key, value] of Object.entries(lookupCommandPos)) {
 | 
				
			||||||
 | 
					        if (value[0] === pos[0] && value[1] === pos[1]) {
 | 
				
			||||||
 | 
					            return key as Command;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    throw new Error(`invalid pos: ${pos}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Path {
 | 
				
			||||||
 | 
					    value: string;
 | 
				
			||||||
 | 
					    constructor(commands: Command[]) {
 | 
				
			||||||
 | 
					        this.value = commands.join("");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    get length() {
 | 
				
			||||||
 | 
					        return this.value.length;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    [Symbol.iterator](): IterableIterator<Command> {
 | 
				
			||||||
 | 
					        return this.value[Symbol.iterator]() as IterableIterator<Command>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    first() {
 | 
				
			||||||
 | 
					        return this.value[0] as Command;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    rest() {
 | 
				
			||||||
 | 
					        const ret = new Path([]);
 | 
				
			||||||
 | 
					        ret.value = this.value.slice(1);
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    map<T>(fn: (cmd: Command) => T) {
 | 
				
			||||||
 | 
					        const result: T[] = [];
 | 
				
			||||||
 | 
					        for (const cmd of this.value) {
 | 
				
			||||||
 | 
					            result.push(fn(cmd as Command));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    flatMap<T>(fn: (cmd: Command) => T[]) {
 | 
				
			||||||
 | 
					        const result: T[] = [];
 | 
				
			||||||
 | 
					        for (const cmd of this.value) {
 | 
				
			||||||
 | 
					            result.push(...fn(cmd as Command));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    reduce<T>(fn: (acc: T, cmd: Command) => T, init: T) {
 | 
				
			||||||
 | 
					        let acc = init;
 | 
				
			||||||
 | 
					        for (const cmd of this.value) {
 | 
				
			||||||
 | 
					            acc = fn(acc, cmd as Command);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return acc;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    get last() {
 | 
				
			||||||
 | 
					        return this.value[this.value.length - 1];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    toString() {
 | 
				
			||||||
 | 
					        return this.value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    toCommands() {
 | 
				
			||||||
 | 
					        return this.value.split("") as Command[];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // ++ is union of two sets.
 | 
				
			||||||
 | 
					    // concat :: cmd -> path_set -> path_set
 | 
				
			||||||
 | 
					    // concat x [] = [x]
 | 
				
			||||||
 | 
					    // concat x (y:ys) = (y:x) ++ (concat x ys)
 | 
				
			||||||
 | 
					    concat(command: Command) {
 | 
				
			||||||
 | 
					        const ret = new Path([]);
 | 
				
			||||||
 | 
					        ret.value = this.value + command;
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    concatPath(path: Path) {
 | 
				
			||||||
 | 
					        const ret = new Path([]);
 | 
				
			||||||
 | 
					        ret.value = this.value + path.value;
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    static from(commands: Command[]) {
 | 
				
			||||||
 | 
					        return new Path(commands);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // \product is a function that returns a list of all possible combinations of two lists.
 | 
				
			||||||
 | 
					    // \product :: path_set -> path_set -> path_set
 | 
				
			||||||
 | 
					    // \product x [] = []
 | 
				
			||||||
 | 
					    // \product (x:xs) y = (concat x y) ++ (\product xs y)
 | 
				
			||||||
 | 
					    // infix 5 \product // associativity is not important. because it commutative.
 | 
				
			||||||
 | 
					    // product :: path_set -> path_set -> path_set
 | 
				
			||||||
 | 
					    // product a b = a \product b
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static product(a: PathSet, b: PathSet): PathSet {
 | 
				
			||||||
 | 
					        return a.flatMap((x) => b.map((y) => x.concatPath(y)));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // it's not pure function.
 | 
				
			||||||
 | 
					    // only use for test.
 | 
				
			||||||
 | 
					    // execute :: (cmd, cmd -> never) -> Option of next_pos
 | 
				
			||||||
 | 
					    execute(start: Command, onAccept: (accept: Command) => void): Pos | null {
 | 
				
			||||||
 | 
					        let pos = commandToPos(start);
 | 
				
			||||||
 | 
					        for (const command of this.toCommands()) {
 | 
				
			||||||
 | 
					            if (command === "A") {
 | 
				
			||||||
 | 
					                onAccept(posToCommand(pos));
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const newPos = moveSingle(pos, command);
 | 
				
			||||||
 | 
					            if (newPos === null) {
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            pos = newPos;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return pos;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    equals(other: Path) {
 | 
				
			||||||
 | 
					        return this.value === other.value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const lookupCommandDelta: Record<Command, Pos> = {
 | 
				
			||||||
 | 
					    "^": [0, -1],
 | 
				
			||||||
 | 
					    "v": [0, 1],
 | 
				
			||||||
 | 
					    "<": [-1, 0],
 | 
				
			||||||
 | 
					    ">": [1, 0],
 | 
				
			||||||
 | 
					    "A": [0, 0],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export function pathToDelta(command: Command): Pos {
 | 
				
			||||||
 | 
					    return lookupCommandDelta[command];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export function moveSingle(pos: Pos, command: Command): Pos | null {
 | 
				
			||||||
 | 
					    const [x, y] = pos;
 | 
				
			||||||
 | 
					    const [dx, dy] = pathToDelta(command);
 | 
				
			||||||
 | 
					    const newPos: [number, number] = [x + dx, y + dy];
 | 
				
			||||||
 | 
					    // out of range
 | 
				
			||||||
 | 
					    if (newPos[0] < 0 || newPos[0] > 2 || newPos[1] < 0 || newPos[1] > 1) {
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // invalid position
 | 
				
			||||||
 | 
					    if (newPos[0] === 0 && newPos[1] === 0) {
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return newPos;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// type Handler 'T :: start_pos -> ('T, next_pos)
 | 
				
			||||||
 | 
					export type Handler<T> = (start: Command) => [T, Command];
 | 
				
			||||||
 | 
					export class PathSetHandler {
 | 
				
			||||||
 | 
					    private fn: Handler<PathSet>;
 | 
				
			||||||
 | 
					    constructor(fn: Handler<PathSet>) {
 | 
				
			||||||
 | 
					        this.fn = fn;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // call :: Handler of start_pos -> (path_set, next_pos)
 | 
				
			||||||
 | 
					    // call f = f
 | 
				
			||||||
 | 
					    call(command: Command) {
 | 
				
			||||||
 | 
					        return this.fn(command);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // mempty :: Handler of path_set
 | 
				
			||||||
 | 
					    // mempty = \s -> ([], s)
 | 
				
			||||||
 | 
					    static empty() {
 | 
				
			||||||
 | 
					        return new PathSetHandler(
 | 
				
			||||||
 | 
					            (s: Command) => [[], s],
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // munit :: Handler of path_set
 | 
				
			||||||
 | 
					    // munit = \s -> ([[]], s)
 | 
				
			||||||
 | 
					    static unit() {
 | 
				
			||||||
 | 
					        return new PathSetHandler(
 | 
				
			||||||
 | 
					            (s: Command) => [[Path.from([])], s],
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    static fromPathSet(t: PathSet) {
 | 
				
			||||||
 | 
					        return new PathSetHandler(
 | 
				
			||||||
 | 
					            (s: Command) => [t, s],
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    coproduct(f: PathSetHandler) {
 | 
				
			||||||
 | 
					        // union of two sets.
 | 
				
			||||||
 | 
					        return new PathSetHandler(
 | 
				
			||||||
 | 
					            (s: Command) => {
 | 
				
			||||||
 | 
					                const [t, s1] = this.fn(s);
 | 
				
			||||||
 | 
					                const [a, s2] = f.call(s1);
 | 
				
			||||||
 | 
					                // merge two sets t and a.
 | 
				
			||||||
 | 
					                const set = new Set([...t, ...a]);
 | 
				
			||||||
 | 
					                const result = Array.from(set);
 | 
				
			||||||
 | 
					                return [result, s2];
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // mproduct :: Handler of path_set -> Handler of path_set -> Handler of path_set
 | 
				
			||||||
 | 
					    // mproduct f g = s ->
 | 
				
			||||||
 | 
					    //  let (t, s') = f s in
 | 
				
			||||||
 | 
					    //  let (a ,s'') = g s'
 | 
				
			||||||
 | 
					    //  in ( (product t a )  , s'')
 | 
				
			||||||
 | 
					    // op ; = mproduct
 | 
				
			||||||
 | 
					    // infixr 5 ;
 | 
				
			||||||
 | 
					    product(f: PathSetHandler) {
 | 
				
			||||||
 | 
					        return new PathSetHandler(
 | 
				
			||||||
 | 
					            (s: Command) => {
 | 
				
			||||||
 | 
					                const [t, s1] = this.fn(s);
 | 
				
			||||||
 | 
					                const [a, s2] = f.fn(s1);
 | 
				
			||||||
 | 
					                return [Path.product(t, a), s2];
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // available_paths ::  (start_pos, target_pos) -> path_set
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// type start_pos = cmd
 | 
				
			||||||
 | 
					// type target_pos = cmd
 | 
				
			||||||
 | 
					// type next_pos = cmd
 | 
				
			||||||
 | 
					// available_paths ::  (start_pos, target_pos) -> path_set
 | 
				
			||||||
 | 
					export function availableShortestPaths(
 | 
				
			||||||
 | 
					    start: Command,
 | 
				
			||||||
 | 
					    target: Command,
 | 
				
			||||||
 | 
					): PathSet {
 | 
				
			||||||
 | 
					    const startPos = commandToPos(start);
 | 
				
			||||||
 | 
					    const targetPos = commandToPos(target);
 | 
				
			||||||
 | 
					    const paths: Path[] = [];
 | 
				
			||||||
 | 
					    const queue: [Path, Pos][] = [[new Path([]), startPos]];
 | 
				
			||||||
 | 
					    const deltaSum = Math.abs(targetPos[0] - startPos[0]) +
 | 
				
			||||||
 | 
					        Math.abs(targetPos[1] - startPos[1]);
 | 
				
			||||||
 | 
					    while (queue.length > 0) {
 | 
				
			||||||
 | 
					        const [path, pos] = queue.shift()!;
 | 
				
			||||||
 | 
					        if (pos[0] === targetPos[0] && pos[1] === targetPos[1]) {
 | 
				
			||||||
 | 
					            paths.push(path);
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (path.length >= deltaSum) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const command of ["^", "v", "<", ">"] as Command[]) {
 | 
				
			||||||
 | 
					            const nextPos = moveSingle(pos, command);
 | 
				
			||||||
 | 
					            if (nextPos) {
 | 
				
			||||||
 | 
					                queue.push([path.concat(command), nextPos]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (paths.length === 0) {
 | 
				
			||||||
 | 
					        throw new Error(`no path found from ${start} to ${target}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return paths;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// move :: cmd -> Handler of path_set
 | 
				
			||||||
 | 
					// move cmd = \s -> (available_paths (s, cmd), cmd)
 | 
				
			||||||
 | 
					export const move: (cmd: Command) => PathSetHandler = (command: Command) => {
 | 
				
			||||||
 | 
					    return new PathSetHandler((start: Command) => {
 | 
				
			||||||
 | 
					        return [availableShortestPaths(start, command), command];
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export const accept: PathSetHandler = PathSetHandler.fromPathSet([
 | 
				
			||||||
 | 
					    Path.from(["A"]),
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					// inputCommand :: cmd -> Handler of path_set
 | 
				
			||||||
 | 
					// inputCommand cmd = (move cmd); accept
 | 
				
			||||||
 | 
					export const inputCommand = (command: Command) => move(command).product(accept);
 | 
				
			||||||
 | 
					// inputCommands :: list of cmd -> Handler of path_set
 | 
				
			||||||
 | 
					// inputCommands xs = foldl (x -> y -> x; y) munit xs
 | 
				
			||||||
 | 
					// or
 | 
				
			||||||
 | 
					// inputCommands [] = munit
 | 
				
			||||||
 | 
					// inputCommands (x:xs) = (inputCommand x) ; (inputCommands xs)
 | 
				
			||||||
 | 
					export const inputCommands = (commands: Path) => {
 | 
				
			||||||
 | 
					    return commands.reduce((acc, command) => {
 | 
				
			||||||
 | 
					        return acc.product(inputCommand(command));
 | 
				
			||||||
 | 
					    }, PathSetHandler.unit());
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// shortest_path_length :: (handler of PathSet) -> handler of Int
 | 
				
			||||||
 | 
					// shortest_path_length h = \s -> (h s) |> foldl ((x,y) -> min(len x,len y)) INF
 | 
				
			||||||
 | 
					export function shortestPathLength(h: PathSetHandler): Handler<number> {
 | 
				
			||||||
 | 
					    return (s: Command) => {
 | 
				
			||||||
 | 
					        const [paths, last] = h.call(s);
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            paths.map((x) => x.length).reduce(
 | 
				
			||||||
 | 
					                (x, y) => Math.min(x, y),
 | 
				
			||||||
 | 
					                Infinity,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            last,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// shortest_path_length has property of shortest path length.
 | 
				
			||||||
 | 
					// shortest_path_length (inputCommands x:xs) s = shortest_path_length (inputCommands [x]) s + shortest_path_length (inputCommands xs) x
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * the following assertion is always true.
 | 
				
			||||||
 | 
					 * ```ts
 | 
				
			||||||
 | 
					 * for (const s of ["^", "v", "<", ">", "A"] as Command[]) {
 | 
				
			||||||
 | 
					 *      // any path
 | 
				
			||||||
 | 
					 *     const paths = makeRandomSnapshotOfPath();
 | 
				
			||||||
 | 
					 *     const [x, ...xs] = paths;
 | 
				
			||||||
 | 
					 *     const s1 = shortestPathLengthRaw(inputCommands(Path.from([x])))(s)[0];
 | 
				
			||||||
 | 
					 *     const s2 = shortestPathLengthRaw(inputCommands(Path.from(xs)))(x)[0];
 | 
				
			||||||
 | 
					 *     const s3 = shortestPathLengthRaw(inputCommands(paths))(s)[0];
 | 
				
			||||||
 | 
					 *     assertEquals(
 | 
				
			||||||
 | 
					 *         s1 + s2,
 | 
				
			||||||
 | 
					 *         s3,
 | 
				
			||||||
 | 
					 *         `s: ${s}, x: ${x}, xs: ${xs}, s1: ${s1}, s2: ${s2}, s3: ${s3}`,
 | 
				
			||||||
 | 
					 *    );
 | 
				
			||||||
 | 
					 * }
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function double(xs: Path) {
 | 
				
			||||||
 | 
					    return (s1: Command) => {
 | 
				
			||||||
 | 
					        return (s2: Command) => {
 | 
				
			||||||
 | 
					            const [pathset] = inputCommands(xs).call(s1);
 | 
				
			||||||
 | 
					            return pathset.flatMap((p) => {
 | 
				
			||||||
 | 
					                const [pathset2] = inputCommands(p).call(s2);
 | 
				
			||||||
 | 
					                return pathset2;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// const data = double(Path.from("<A^A>^^AvvvA".split("") as Command[]))("A")("A");
 | 
				
			||||||
 | 
					// console.log(data.map((x) => x.toString()));
 | 
				
			||||||
 | 
					// double xs = \s1 -> \s2 -> flatMap (\cmd -> fst.inputCommands cmd s2) (fst.inputCommands xs s1)
 | 
				
			||||||
 | 
					// = \s1 -> \s2 ->  flatMap ((\cmd -> fst.inputCommands cmd s2) (fst.inputCommands x:xs s1))
 | 
				
			||||||
 | 
					// = \s1 -> \s2 ->  flatMap (\cmd -> fst.inputCommands cmd s2) ((fst.inputCommand x; fst.inputCommands xs) s1)
 | 
				
			||||||
 | 
					// = \s1 -> \s2 ->  flatMap (\cmd -> fst.inputCommands cmd s2) ((move x; accept; fst.inputCommands xs) s1)
 | 
				
			||||||
 | 
					// = \s1 -> \s2 ->  flatMap (\cmd -> fst.inputCommands cmd s2) (
 | 
				
			||||||
 | 
					//      let (p,s3) = (move x; accept; s1) in
 | 
				
			||||||
 | 
					//      p \product (inputCommands xs s3)
 | 
				
			||||||
 | 
					// )
 | 
				
			||||||
 | 
					// = \s1 -> \s2 ->  flatMap (\cmd -> fst.inputCommands cmd s2) (
 | 
				
			||||||
 | 
					//      let (p,s3) = (concat (move x s1) 'A') in
 | 
				
			||||||
 | 
					//      p \product (inputCommands xs s3)
 | 
				
			||||||
 | 
					// )
 | 
				
			||||||
 | 
					// = \s1 -> \s2 -> (
 | 
				
			||||||
 | 
					//      let (p,s3) = (concat (move x s1) 'A') in
 | 
				
			||||||
 | 
					//      flatMap (\cmd -> fst.inputCommands cmd s2) p \product flatMap (\cmd -> fst.inputCommands cmd s2) (fst.inputCommands xs s3)
 | 
				
			||||||
 | 
					// )
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// shortest_path_length \s' -> (double xs 'A' s') 'A'
 | 
				
			||||||
 | 
					// = shortest_path_length \s' -> (
 | 
				
			||||||
 | 
					//      let (p,s3) = (concat (move x 'A') 'A') in
 | 
				
			||||||
 | 
					//      flatMap (\cmd -> fst.inputCommands cmd s') p \product flatMap (\cmd -> fst.inputCommands cmd 's) (fst.inputCommands xs s3)
 | 
				
			||||||
 | 
					// ) 'A'
 | 
				
			||||||
 | 
					// = (
 | 
				
			||||||
 | 
					//      let (p,s3) = (concat (move x 'A') 'A') in
 | 
				
			||||||
 | 
					//      let paths = flatMap (\cmd -> fst.inputCommands cmd 'A') p \product flatMap (\cmd -> fst.inputCommands cmd 'A') (fst.inputCommands xs s3)
 | 
				
			||||||
 | 
					//      in
 | 
				
			||||||
 | 
					//      paths |> foldl ((x,y) -> min(len x,len y)) INF
 | 
				
			||||||
 | 
					// )
 | 
				
			||||||
 | 
					// = (
 | 
				
			||||||
 | 
					//      let handler2 = \cmd -> fst.inputCommands cmd 'A' in
 | 
				
			||||||
 | 
					//      let (p,s3) = (concat (move x 'A') 'A') in
 | 
				
			||||||
 | 
					//      let paths = flatMap handler2 p \product flatMap handler2 (fst.inputCommands xs s3) in
 | 
				
			||||||
 | 
					//      paths |> set.min (\x -> len x)
 | 
				
			||||||
 | 
					// )
 | 
				
			||||||
 | 
					// = (
 | 
				
			||||||
 | 
					//     let handler2 = \cmd -> fst.inputCommands cmd 'A' in
 | 
				
			||||||
 | 
					//     let (p,s3) = (concat (move x 'A') 'A') in
 | 
				
			||||||
 | 
					//     set.min (\x -> len x)
 | 
				
			||||||
 | 
					//       (flatMap handler2 p \product flatMap handler2 (fst.inputCommands xs s3))
 | 
				
			||||||
 | 
					// = (
 | 
				
			||||||
 | 
					//     let handler2 = \cmd -> fst.inputCommands cmd 'A' in
 | 
				
			||||||
 | 
					//     let (p,s3) = (concat (move x 'A') 'A') in
 | 
				
			||||||
 | 
					//     (set.min (\x -> len x)
 | 
				
			||||||
 | 
					//         (flatMap handler2 p)) +
 | 
				
			||||||
 | 
					//     (set.min (\x -> len x) (flatMap handler2 (fst.inputCommands xs s3))
 | 
				
			||||||
 | 
					// )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function shortL(
 | 
				
			||||||
 | 
					    curPos: Command,
 | 
				
			||||||
 | 
					    command: Command,
 | 
				
			||||||
 | 
					): number {
 | 
				
			||||||
 | 
					    const [n] = shortestPathLength(inputCommands(Path.from([command])))(curPos);
 | 
				
			||||||
 | 
					    return n;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const memo = new Map<string, number>();
 | 
				
			||||||
 | 
					function myShortestPathLength(
 | 
				
			||||||
 | 
					    depth: number,
 | 
				
			||||||
 | 
					    cmds: Path,
 | 
				
			||||||
 | 
					): number {
 | 
				
			||||||
 | 
					    const key = cmds.toString() + depth;
 | 
				
			||||||
 | 
					    if (memo.has(key)) {
 | 
				
			||||||
 | 
					        return memo.get(key)!;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (depth === 1) {
 | 
				
			||||||
 | 
					        const ret = shortestPathLength(inputCommands(cmds))("A")[0];
 | 
				
			||||||
 | 
					        memo.set(key, ret);
 | 
				
			||||||
 | 
					        return ret;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const ret = cmds.reduce((acc, cmd) => {
 | 
				
			||||||
 | 
					        const handler = inputCommand(cmd);
 | 
				
			||||||
 | 
					        const [pathset, last] = handler.call(acc.last);
 | 
				
			||||||
 | 
					        const n = pathset.map((x) => myShortestPathLength(depth - 1, x)).reduce(
 | 
				
			||||||
 | 
					            (x, y) => Math.min(x, y),
 | 
				
			||||||
 | 
					            Infinity,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            last: last,
 | 
				
			||||||
 | 
					            sum: acc.sum + n,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        last: "A" as Command,
 | 
				
			||||||
 | 
					        sum: 0,
 | 
				
			||||||
 | 
					    }).sum;
 | 
				
			||||||
 | 
					    memo.set(key, ret);
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (import.meta.main) {
 | 
				
			||||||
 | 
					    const example = Path.from("<A^A>^^AvvvA".split("") as Command[]);
 | 
				
			||||||
 | 
					    console.log(myShortestPathLength(1, example));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const codes = Deno.readTextFileSync("input.txt").replaceAll("\r", "").split(
 | 
				
			||||||
 | 
					        "\n",
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let total = 0;
 | 
				
			||||||
 | 
					    for (const code of codes) {
 | 
				
			||||||
 | 
					        let start = NUMPAD_FIRST_POS;
 | 
				
			||||||
 | 
					        for (const num of code.split("")) {
 | 
				
			||||||
 | 
					            console.log("num", num);
 | 
				
			||||||
 | 
					            const numpadPaths = shortestPathesOnNumpadRobot({ pos: start }, num)
 | 
				
			||||||
 | 
					                .map((p) => p.concat("A"));
 | 
				
			||||||
 | 
					            if (numpadPaths.length === 0) {
 | 
				
			||||||
 | 
					                throw new Error(`No path found for ${num} from ${start}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            console.log("avail path", numpadPaths.map((p) => p.join("")));
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            let min = Infinity;
 | 
				
			||||||
 | 
					            for (const path of numpadPaths) {
 | 
				
			||||||
 | 
					                const len = myShortestPathLength(25, Path.from(path));
 | 
				
			||||||
 | 
					                min = Math.min(min, len);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const n = parseInt(code.slice(0, 3));
 | 
				
			||||||
 | 
					            console.log("len", min, "n", n);
 | 
				
			||||||
 | 
					            total += n * min;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            start = getPosFromNumpadNumber(num);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    console.log("total", total);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue