From c3b509b9692bfd7372a93836cb21db4b901508be Mon Sep 17 00:00:00 2001 From: Dazbo Date: Wed, 18 Dec 2024 00:12:57 +0000 Subject: [PATCH] 2024 d17 documented --- .../Dazbo's_Advent_of_Code_2024.ipynb | 230 +++++++++++++++--- 1 file changed, 197 insertions(+), 33 deletions(-) diff --git a/src/AoC_2024/Dazbo's_Advent_of_Code_2024.ipynb b/src/AoC_2024/Dazbo's_Advent_of_Code_2024.ipynb index d26ca51..f40d698 100644 --- a/src/AoC_2024/Dazbo's_Advent_of_Code_2024.ipynb +++ b/src/AoC_2024/Dazbo's_Advent_of_Code_2024.ipynb @@ -64,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 148, + "execution_count": 2, "metadata": { "id": "p5Ki_HvOJUWk", "tags": [] @@ -254,7 +254,7 @@ }, { "cell_type": "code", - "execution_count": 151, + "execution_count": 5, "metadata": { "id": "lwP0r3BAaxjt", "tags": [] @@ -327,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": 152, + "execution_count": 6, "metadata": { "id": "Y6nbd6WMryWi", "tags": [] @@ -355,7 +355,7 @@ }, { "cell_type": "code", - "execution_count": 153, + "execution_count": 7, "metadata": { "id": "A8sU4Ez_bBKl", "tags": [] @@ -525,7 +525,7 @@ }, { "cell_type": "code", - "execution_count": 154, + "execution_count": 8, "metadata": { "id": "DT5FSYliC9wp", "tags": [] @@ -6195,7 +6195,7 @@ }, { "cell_type": "code", - "execution_count": 200, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -6317,7 +6317,7 @@ }, { "cell_type": "code", - "execution_count": 201, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -6436,14 +6436,14 @@ "source": [ "#### Simplifying Instructions\n", "\n", - "So we need to work out what the program actually does and reverse engineer to get the solution.\n", + "We can massively speed up our brute force by working out what the program actually does. Then we can write a simplified set of PYthon instructions.\n", "\n", - "If we take the sample data, we can convert our input program into Python instructions. This gives us the right answer quickly with the sample data:" + "This gives us the right answer quickly with the sample data:" ] }, { "cell_type": "code", - "execution_count": 203, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -6496,7 +6496,7 @@ }, { "cell_type": "code", - "execution_count": 244, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -6518,7 +6518,7 @@ " \n", " return out\n", "\n", - "def solve_part2_for_real(data):\n", + "def brute_force_part2_for_real(data):\n", " \"\"\" Take our real data and run it through our custom program. \"\"\"\n", " _, program = process_input(data)\n", " logger.debug(program)\n", @@ -6543,7 +6543,7 @@ "source": [ "# This will take forever!!\n", "logger.setLevel(logging.DEBUG)\n", - "solve_part2_for_real(input_data)" + "brute_force_part2_for_real(input_data)" ] }, { @@ -6552,16 +6552,37 @@ "source": [ "#### Reverse Engineering\n", "\n", - "Let's reverse engineer with my program: \n", - "`2,4,1,6,7,5,4,6,1,4,5,5,0,3,3,0`\n", + "We can run our program in reverse, to determine the required value of `a`. I'm working with `2,4,1,6,7,5,4,6,1,4,5,5,0,3,3,0`.\n", "\n", - "We can run our program in reverse, to determine the required value of `a`. \n", + "Remember that our program starts with a value of `a`, and then performs a number of iterations. It generates an output with each iteration. And it only completes when `a` is 0.\n", "\n", - "- Observe that our program terminates when register `a` has a value of 0. So we start the reverse process by setting register `a` to 0. \n", - "- Then we need to determine which _previous_ values of `a` will output `0`. Why `0`? Because this is the last output of our program.\n", + "- So we can start the reverse process by setting register `a` to 0. \n", + "- Then we need to determine which _previous_ values of `a` would _output_ `0`. Why `0`? Because this is the last output of our program.\n", "- The _last_ instruction in our program is **the only instruction that modifies `a`**. (All other instructions modify other registers.) So to get to the previous value of `a`, we only need to reverse this last instruction!\n", "- The last instrution is `a // 8`, which is equivalent to `a >> 3`. I.e. it strips the last 3 bits of our number. So to reverse it, we need to add 3 bits back in. Since `2**3 = 8`, this means there are 8 combinations of bits we need to add to our value of `a` to try. E.g. if `a == 0o0000`, then we can try each of `0o0000, 0o0001, 0o0010, 0o0011, 0o0100, 0o0101, 0o0110, 0o0111`.\n", - "- So try each of these values by inserting it back at the top of our program, and testing if the output is the required digit. If so, we add it into a _new_ set of values to try, to get the _next_ output.\n" + "- So try each of these values by inserting it back at the top of our program, and testing if the _first digit_ of the output is the required digit. (Because each time we run the program, our output will grow by one.) If this is the required digit we add this value of `a` into a _new_ set of values to try, to get the _next_ output. Note that the number of values to try for a given output digit will never exceed 8.\n", + "- Finally, when we've processed the last digit in our (reversed) program, we can simply return the minimum value of the current valid values for register `a`.\n", + "\n", + "The output looks like this:\n", + "\n", + "```text\n", + "valid_vals_for_a={0}\n", + "out=[0]\n", + "valid_vals_for_a={2}\n", + "out=[3, 0]\n", + "valid_vals_for_a={17, 20}\n", + "out=[3, 3, 0]\n", + "valid_vals_for_a={165}\n", + "out=[0, 3, 3, 0]\n", + "valid_vals_for_a={1323}\n", + "out=[5, 0, 3, 3, 0]\n", + "valid_vals_for_a={10586}\n", + "out=[5, 5, 0, 3, 3, 0]\n", + "valid_vals_for_a={84690, 84693}\n", + "out=[4, 5, 5, 0, 3, 3, 0]\n", + "```\n", + "\n", + "And so on.\n" ] }, { @@ -6576,29 +6597,161 @@ " _, program = process_input(data)\n", "\n", " logger.info(f\"{program=}\")\n", - " to_try = {0} # set of possible reg a values to try\n", - " assert len(to_try) <= 8, \"There should never be more than 8 values to try for an output\"\n", + " valid_vals_for_a = {0} # set of reg a values that generated the last digit\n", + " assert len(valid_vals_for_a) <= 8, \"There should never be more than 8 values to try for an output\"\n", "\n", " for required_instr in reversed(program): # Will give us 0, 3, 3, 0, 5...\n", - " new_to_try = set()\n", + " logger.debug(f\"{valid_vals_for_a=}\")\n", + " next_vals_for_a = set()\n", " \n", - " for a in to_try:\n", - " a_shifted = a * 8 # Equivalent to left shift by 3, so can now add any three bits\n", + " for a_val in valid_vals_for_a:\n", + " a_shifted = a_val * 8 # Equivalent to left shift by 3, so can now add any three bits\n", " \n", - " for modifier in range(8): # Try all 8 values of a, e.g. 0-7\n", - " out = run_prog_for_a(a_shifted + modifier)\n", - " if out and out[0] == required_instr: # If we've output the correct value\n", - " new_to_try.add(a_shifted + modifier)\n", - " \n", - " to_try = new_to_try\n", + " for candidate_a in range(a_shifted, a_shifted+8): # Try all 8 values of a+modifier\n", + " out = run_prog_for_a(candidate_a) # Run single cycle\n", + " if out and out[0] == required_instr: # If we've output the required value\n", + " good_out = out # just for printing later\n", + " next_vals_for_a.add(candidate_a) # this value is good\n", + " \n", + " logger.debug(f\"out={good_out}\") \n", + " valid_vals_for_a = next_vals_for_a\n", " \n", - " return min(to_try)\n", + " return min(valid_vals_for_a)\n", "\n", "required_a = reverse_engineer(input_data)\n", "logger.info(f\"{required_a=}\")\n", " " ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, let's substitute our value of `a` back into the input and see if it works." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "part_2_test = \"\"\"\\\n", + "Register A: 90938893795561\n", + "Register B: 0\n", + "Register C: 0\n", + "\n", + "Program: 2,4,1,6,7,5,4,6,1,4,5,5,0,3,3,0\"\"\"\n", + "part_2_ans = \"2,4,1,6,7,5,4,6,1,4,5,5,0,3,3,0\"\n", + "\n", + "logger.setLevel(logging.DEBUG)\n", + "validate(solve_part1(part_2_test), part_2_ans)\n", + "logger.info(\"Part 2 validation successful!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Day 18: title" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "DAY = \"18\" # replace with actual number (without leading digit)\n", + "show_day_link(DAY)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "d_name = \"d\" + str(DAY).zfill(2) # e.g. d01\n", + "script_name = \"aoc\" + str(YEAR) + d_name # e.g. aoc2024d01\n", + "locations = dc.get_locations(d_name)\n", + "logger.setLevel(logging.DEBUG)\n", + "# td.setup_file_logging(logger, locations.output_dir)\n", + "\n", + "# Retrieve input and store in local file\n", + "try:\n", + " write_puzzle_input_file(YEAR, DAY, locations)\n", + " with open(locations.input_file, mode=\"rt\") as f:\n", + " input_data = f.read().splitlines()\n", + "\n", + " logger.info(\"Input data:\\n%s\", dc.top_and_tail(input_data))\n", + "except ValueError as e:\n", + " logger.error(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Day 18 Part 1\n", + "\n", + "Overview..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def solve_part1(data):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "sample_inputs = []\n", + "sample_inputs.append(\"\"\"abcdef\"\"\")\n", + "sample_answers = [\"uvwxyz\"]\n", + "\n", + "logger.setLevel(logging.DEBUG)\n", + "for curr_input, curr_ans in zip(sample_inputs, sample_answers):\n", + " validate(solve_part1(curr_input), curr_ans) # test with sample data\n", + " logger.info(\"Test passed\")\n", + "\n", + "logger.info(\"All tests passed!\")\n", + "\n", + "logger.setLevel(logging.INFO)\n", + "soln = solve_part1(input_data)\n", + "logger.info(f\"Part 1 soln={soln}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Day 18 Part 2\n", + "\n", + "Overview..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def solve_part2(data):\n", + " pass" + ] + }, { "cell_type": "code", "execution_count": null, @@ -6606,8 +6759,19 @@ "outputs": [], "source": [ "%%time\n", - "# logger.setLevel(logging.INFO)\n", - "soln = solve_part2_for_real(input_data)\n", + "sample_inputs = []\n", + "sample_inputs.append(\"\"\"abcdef\"\"\")\n", + "sample_answers = [\"uvwxyz\"]\n", + "\n", + "logger.setLevel(logging.DEBUG)\n", + "for curr_input, curr_ans in zip(sample_inputs, sample_answers):\n", + " validate(solve_part2(curr_input), curr_ans) # test with sample data\n", + " logger.info(\"Test passed\") \n", + "\n", + "logger.info(\"Tests passed!\")\n", + "\n", + "logger.setLevel(logging.INFO)\n", + "soln = solve_part2(input_data)\n", "logger.info(f\"Part 2 soln={soln}\")" ] },