Skip to content

Commit

Permalink
2024 d17 documented
Browse files Browse the repository at this point in the history
  • Loading branch information
derailed-dash committed Dec 18, 2024
1 parent 8d7f09e commit c3b509b
Showing 1 changed file with 197 additions and 33 deletions.
230 changes: 197 additions & 33 deletions src/AoC_2024/Dazbo's_Advent_of_Code_2024.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
},
{
"cell_type": "code",
"execution_count": 148,
"execution_count": 2,
"metadata": {
"id": "p5Ki_HvOJUWk",
"tags": []
Expand Down Expand Up @@ -254,7 +254,7 @@
},
{
"cell_type": "code",
"execution_count": 151,
"execution_count": 5,
"metadata": {
"id": "lwP0r3BAaxjt",
"tags": []
Expand Down Expand Up @@ -327,7 +327,7 @@
},
{
"cell_type": "code",
"execution_count": 152,
"execution_count": 6,
"metadata": {
"id": "Y6nbd6WMryWi",
"tags": []
Expand Down Expand Up @@ -355,7 +355,7 @@
},
{
"cell_type": "code",
"execution_count": 153,
"execution_count": 7,
"metadata": {
"id": "A8sU4Ez_bBKl",
"tags": []
Expand Down Expand Up @@ -525,7 +525,7 @@
},
{
"cell_type": "code",
"execution_count": 154,
"execution_count": 8,
"metadata": {
"id": "DT5FSYliC9wp",
"tags": []
Expand Down Expand Up @@ -6195,7 +6195,7 @@
},
{
"cell_type": "code",
"execution_count": 200,
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
Expand Down Expand Up @@ -6317,7 +6317,7 @@
},
{
"cell_type": "code",
"execution_count": 201,
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -6496,7 +6496,7 @@
},
{
"cell_type": "code",
"execution_count": 244,
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -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",
Expand All @@ -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)"
]
},
{
Expand All @@ -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"
]
},
{
Expand All @@ -6576,38 +6597,181 @@
" _, 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,
"metadata": {},
"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}\")"
]
},
Expand Down

0 comments on commit c3b509b

Please sign in to comment.