Have the old fellow heard of it? https://adventofcode.com/ This website, on the eve of Christmas every day, will start publishing programming puzzles for 25 consecutive days, attracting countless people to participate. Since I began to learn programming, I have used the topics here to exercise my programming ability.
The difficulty of the topic in 2021 is gradually deepened, and it becomes more and more difficult in the future. I insisted on completing the topic in the first 23 days until I saw the topic on the 24th day. After a long time of stumbling, I still can't think of it.
The problem is like this: there is a computing unit with four registers W, x, y and Z, which supports the following instructions:
- inp a (read the number entered by the user and save it in register a)
- Add a b (the sum of a and b is stored in A. b can be a number or a register)
- mul a b (... Product...)
- mod a b (... Remainder...)
- div a b (... Divide...)
- eql a b (1 if a equals b, otherwise 0. The result is saved in A. b can be a number or a register)
Then, given a series of instructions, make the user input a series of numbers from 1 to 9, so that the result of register z is equal to 0 The sum of the user's input numbers is the smallest in decimal order.
Try to solve the problem
At first, I tried to optimize the given instructions, such as add a 0 and mul a 1, which can be omitted directly. Finally, I found that there was still a long string after optimization, which was useless.
Later, when you think of div a b, it is 0 if a < B. Plus we know that the range of a single input is 1 9. In the future, it can be optimized according to the range of numbers, and finally get the range of z.
I can't make progress here. After a month, I finally can't stand it. I checked the problem-solving methods shared by you on reddit. The main method is to reverse analyze the given instructions to obtain the constraints on the input value. I'm a little disappointed to see this. Although reverse analysis is also cool, there are few assumptions about input in the previous topics of AOC. I prefer to use a general solution that can be applied to any instruction list.
Finally, I saw the of some great God solution , it perfectly meets my needs.
solution
The main idea is the same as before, that is, to analyze the maximum and minimum values of the results of each calculation. The best thing is that the great God does not only analyze once, but reanalyzes the range of calculation results every time the inp a instruction reads the value entered by the user. Let i(n) be the user input read by the nth inp instruction. For example, when we do not give the value of i(1), the range of i(1) is {1,9}, and the range of z finally analyzed may be a large interval. But if we give i(1) as a constant, the range of z may be much smaller.
By analogy, every time we give a value of i, we do an analysis. If the range of z does not include 0, we know that the sequence of i does not need to continue this time. On the contrary, you can continue to give the value of the next i.
The following is the complete code
inputs = File.read!("inputs/d24.dat") defmodule S do @moduledoc """ Thanks ephemient's excellent answer! Rewrote from https://github.com/ephemient/aoc2021/blob/main/rs/src/day24.rs . """ @doc """ Parse instructions. """ def parse(str) do str |> String.split("\n") |> Enum.map(fn line -> case String.split(line, " ") do [h | t] -> {parse_op(h), parse_args(t)} end end) end defp parse_op(op) when op in ~w(inp add mul div mod eql), do: String.to_atom(op) defp parse_args(list) do list |> Enum.map(fn x -> if x in ~w(w x y z) do String.to_atom(x) else String.to_integer(x) end end) end def new_alu, do: %{w: 0, x: 0, y: 0, z: 0} @nothing :nothing def check_range(ins, alu) do alu = for {r, v} <- alu, into: %{} do {r, {v, v}} end alu = ins |> Enum.reduce_while(alu, fn inst, alu -> case inst do {:inp, [lhs]} -> {:cont, %{alu | lhs => {1, 9}}} {op, [lhs, rhs]} -> {a, b} = alu[lhs] {c, d} = alu[rhs] || {rhs, rhs} lhs_range = case op do :add -> {a + c, b + d} :mul -> Enum.min_max([a * c, a * d, b * c, b * d]) :div -> cond do c > 0 -> {div(a, d), div(b, c)} d < 0 -> {div(b, d), div(a, c)} true -> @nothing end :mod -> if c > 0 and c == d do if b - a + 1 < c and rem(a, c) <= rem(b, c) do {rem(a, c), rem(b, c)} else {0, c - 1} end else @nothing end :eql -> cond do a == b and c == d and a == c -> {1, 1} a <= d and b >= c -> {0, 1} true -> {0, 0} end end case lhs_range do {a, b} -> {:cont, %{alu | lhs => {a, b}}} @nothing -> {:halt, @nothing} end end end) case alu do @nothing -> @nothing %{z: {a, b}} -> a <= 0 and b >= 0 end end def solve([], _, prefix, alu) do if alu.z == 0 do prefix else nil end end def solve([inst | rest], nums, prefix, alu) do IO.inspect(prefix, label: "prefix") case inst do {:inp, [lhs]} -> nums |> Enum.find_value(fn num -> alu = %{alu | lhs => num} if check_range(rest, alu) != false do solve(rest, nums, 10 * prefix + num, alu) else nil end end) {op, [lhs, rhs]} -> a = alu[lhs] b = alu[rhs] || rhs result = case op do :add -> a + b :mul -> a * b :div -> div(a, b) :mod -> rem(a, b) :eql -> if(a == b, do: 1, else: 0) end solve(rest, nums, prefix, %{alu | lhs => result}) end end end # test insts = inputs |> S.parse() # part 1 S.solve(insts, Enum.to_list(9..1), 0, S.new_alu()) |> IO.inspect() # part 2 S.solve(insts, Enum.to_list(1..9), 0, S.new_alu()) |> IO.inspect()