Powered by AppSignal & Oban Pro

d24

d24/d24.livemd

d24

Section

ExUnit.start()

defmodule D24 do
  def parse(input) do
    [top, bottom] = String.split(input, "\n\n")

    initial =
      for line <- String.split(top, "\n"), into: %{} do
        [wire, value] = String.split(line, ": ")

        {wire,
         case value do
           "0" -> false
           "1" -> true
         end}
      end

    gates =
      for line <- String.split(bottom, "\n", trim: true), into: %{} do
        [x, opname, y, "->", out] = String.split(line, " ")

        op =
          case opname do
            "AND" -> :&amp;
            "OR" -> :|
            "XOR" -> :^
          end

        {out, {op, x, y}}
      end

    {initial, gates}
  end

  def calc(op, x, y) do
    case op do
      :&amp; -> x &amp;&amp; y
      :| -> x || y
      :^ -> x != y
    end
  end

  def step({values, gates}) do
    Enum.reduce(gates, {values, %{}}, fn {out, {op, x, y} = gate}, {values, gates} ->
      {xval, yval} = {values[x], values[y]}

      if is_nil(xval) or is_nil(yval) do
        {values, Map.put(gates, out, gate)}
      else
        {Map.put(values, out, calc(op, xval, yval)), gates}
      end
    end)
  end

  def part1(parsed) do
    Stream.iterate(parsed, &amp;D24.step/1)
    |> Stream.drop_while(fn {_, gates} -> !Enum.empty?(gates) end)
    |> Enum.at(0)
    |> elem(0)
    |> Enum.filter(fn {key, _} -> String.starts_with?(key, "z") end)
    |> Enum.sort()
    |> Enum.reverse()
    |> Enum.map(fn
      {_, false} -> "0"
      {_, true} -> "1"
    end)
    |> Enum.join()
    |> String.to_integer(2)
  end

  def norm({op, a, b}), do: if(a < b, do: {op, a, b}, else: {op, b, a})

  use ExUnit.Case

  test "small" do
    input = "x00: 1
x01: 1
x02: 1
y00: 0
y01: 1
y02: 0

x00 AND y00 -> z00
x01 XOR y01 -> z01
x02 OR y02 -> z02
"
    assert 4 == parse(input) |> part1()
  end
end

ExUnit.run()
input = File.read!(__DIR__ <> "/input")
D24.parse(input) |> D24.part1()
import D24

gates = D24.parse(input) |> elem(1)
{:^, "x00", "y00"} = gates["z00"]

{:^, a, b} = gates["z01"] # ghh gnn
{:^, "x01", "y01"} = gates[a]
{:&amp;, "x00", "y00"} = gates[b]

{:^, a, b} = gates["z02"] # msp fwk
{:|, c, d} = gates[a] # nck qhq
{:&amp;, "x01", "y01"} = gates[c]
{:&amp;, e, f} = gates[d] # ghh gnn
# {:^, "x01", "y01"} = gates[e]
# {:&, "x00", "y00"} = gates[f]
{:^, "y02", "x02"} = gates[b]

{:^, a, b} = gates["z03"] # gmp rgn
{:|, c, d} = gates[a] # gpn vbs
{:&amp;, "x02", "y02"} = gates[c]
{:&amp;, e, f} = gates[d] # fwk msp
# {:^, "y02", "x02"} = gates[e]
# {:|, "nck", "qhq"} = gates[f]
{:^, "y03", "x03"} = gates[b]

# 00..45
Map.keys(gates) |> Enum.filter(&amp;String.starts_with?(&amp;1, "z")) |> Enum.sort()

Enum.reduce(2..44, {nil, []}, fn i, {carry, errors} ->
  prev = String.pad_leading("#{i - 1}", 2, "0")
  num = String.pad_leading("#{i}", 2, "0")
  if elem(gates["z#{num}"], 0) == :^ do
    {:^, a, b} = gates["z#{num}"]
    next_carry = Enum.sort([a, b])
    {a, b} = if match?({:^, _, _}, gates[a]), do: {a, b}, else: {b, a}
    if elem(gates[a], 0) == :^ do
      {:^, c, d} = gates[a]
      true = (Enum.sort([c, d]) == ["x#{num}", "y#{num}"])
      if elem(gates[b], 0) == :| do
        {:|, a, b} = gates[b]
        {a, b} = if {:&amp;, "x#{prev}", "y#{prev}"} == norm(gates[a]), do: {a, b}, else: {b, a}
        if {:&amp;, "x#{prev}", "y#{prev}"} == norm(gates[a]) do
          if elem(gates[b], 0) == :&amp; do
            {:&amp;, a, b} = gates[b]
            if !is_nil(carry), do: ^carry = Enum.sort([a, b])
            {next_carry, errors}
          else
            {next_carry, [{i, b} | errors]}
          end
        else
          {next_carry, [{i, a} | errors]}
        end
      else
        {next_carry, [{i, b} | errors]}
      end
    else
      {next_carry, [{i, a} | errors]}
    end
  else
    {nil, [{i, "z#{num}"} | errors]}
  end
end)
|> elem(1)
|> Enum.map(&amp;elem(&amp;1, 1))
|> Enum.sort()
|> Enum.join(",")