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()

Ljzn
399 声望102 粉丝

网络安全;函数式编程;数字货币;人工智能