# The most painful programming problem in 2021

Posted by danielholmes85 on Tue, 15 Feb 2022 04:43:50 +0100

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
{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
: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()```

Topics: leetcode