Have you heard of the website https://adventofcode.com/ , every day before Christmas Eve, it will start publishing programming puzzles for 25 consecutive days, attracting countless people to participate. Since I started learning programming, I have used the topics here to exercise my programming skills.
The difficulty of the questions in 2021 is gradually deepening, and the more difficult it is going forward. I insisted on completing the questions for the first 23 days, until I saw the questions on the 24th day. After a long time, I still can't think of it.
The title is probably like this: There is a computing unit with four registers of w, x, y, and z, which supports the following instructions:
- inp a (reads the number entered by the user and saves it in register a)
- add ab (the sum of a and b is stored in a. b can be a number or a register)
- mul ab (...product...)
- mod ab (...remainder...)
- div ab (...divide...)
- eql ab (1 if a equals b, 0 otherwise. The result is stored in a. b can be a number or a register)
Then given a series of instructions, let the user input a series of numbers from 1 to 9, and make the result of register z equal to 0. Find the maximum and minimum values of the decimal numbers formed by the numbers entered by the user in sequence.
try to solve
At the beginning, I tried to optimize the given instructions, such as add a 0
, mul a 1
can be omitted, and finally found that the optimization is still a long list, useless.
When I think of div a b
later, it is 0 if a < b. In addition, we know that the range of a single input is 1..9, and we can optimize it according to the range of numbers in the future, and finally get the range of z.
I was stuck here and couldn't make progress. After a month, I finally couldn't take it anymore. I checked the solution methods shared by everyone on reddit. The main method is to reverse-engineer a given instruction to derive constraints on the input value. I'm a little disappointed to see this, although reverse analysis is cool, but AOC's previous questions rarely make assumptions about the input, I prefer to use a general solution that can be applied to any list of instructions.
Finally, I saw the solution of by a great god, which perfectly met my needs.
solution
The main idea is the same as before, which is to analyze the maximum and minimum values of the results of each calculation. The best part is that God does not only analyze it once, but every time the inp a
command reads a value input by the user, it re-analyzes the range of the calculation result. 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 may be a large interval after analysis. But if we give i(1) as a constant, the range of z that is finally analyzed may be much smaller.
And so on, 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. Otherwise, you can continue to give the next value of i.
Below 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()
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。