Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 21

day21.livemd

Day 21

Setup

https://adventofcode.com/2022/day/21

defmodule Load do
  def input do
    File.read!(Path.join(Path.absname(__DIR__), "input/21.txt"))
  end
end
defmodule Parse do
  def parse_line(line) do
    [name, value] =
      line
      |> String.split(": ")

    val =
      cond do
        String.contains?(value, " ") -> value |> String.split(" ", trim: true) |> List.to_tuple()
        true -> elem(Integer.parse(value), 0)
      end

    {name, val}
  end

  def input(input_str) do
    input_str
    |> String.split("\n", trim: true)
    |> Enum.map(&parse_line/1)
    |> Map.new()
  end
end

test_input =
  """
  root: pppw + sjmn
  dbpl: 5
  cczh: sllz + lgvd
  zczc: 2
  ptdq: humn - dvpt
  dvpt: 3
  lfqf: 4
  humn: 5
  ljgn: 2
  sjmn: drzm * dbpl
  sllz: 4
  pppw: cczh / lfqf
  lgvd: ljgn * ptdq
  drzm: hmdt - zczc
  hmdt: 32
  """
  |> Parse.input()
real_input =
  Load.input()
  |> Parse.input()

Part 1

defmodule Part1 do
  def op_for_string(str) do
    case str do
      "+" -> fn x, y -> x + y end
      "-" -> fn x, y -> x - y end
      "/" -> fn x, y -> div(x, y) end
      "*" -> fn x, y -> x * y end
    end
  end

  def get_value(map, name) do
    case map[name] do
      {a, op, b} -> op_for_string(op).(get_value(map, a), get_value(map, b))
      int -> int
    end
  end

  def solve(input) do
    get_value(input, "root")
  end
end

Part1.solve(test_input)
Part1.solve(real_input)

Part 2

defmodule Part2 do
  def op_for_string(str) do
    case str do
      "+" -> fn x, y -> x + y end
      "-" -> fn x, y -> x - y end
      "/" -> fn x, y -> div(x, y) end
      "*" -> fn x, y -> x * y end
    end
  end

  def get_value(map, name, humn_val) do
    case name do
      "humn" ->
        humn_val

      _ ->
        case map[name] do
          {a, op, b} ->
            op_for_string(op).(get_value(map, a, humn_val), get_value(map, b, humn_val))

          int ->
            int
        end
    end
  end

  def solve(input) do
    {a, _, b} = input["root"]

    # use 0..1000 for test real_input
    # This is not elegant in the slightest, but I printed the results of a brute-force
    # approach to see if I could spot a trend, and once I did (in the real data, a_val
    # decreases as the humn value incrases), I narrowed the brute forcing to that area
    3_757_272_000_000..3_757_300_000_000
    |> Enum.find(fn val ->
      a_val = get_value(input, a, val)
      b_val = get_value(input, b, val)

      # IO.puts(inspect(val) <> ": " <> inspect(a_val) <> " " <> inspect(b_val))
      a_val == b_val
    end)
  end
end

Part2.solve(real_input)
# Part2.solve(test_input)
Part2.solve(real_input)