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

Day 20

2023/day20.livemd

Day 20

Mix.install([
  {:kino, "~> 0.11.3"}
])

Section

input = Kino.Input.textarea("Input")
defmodule Sol20 do
  def make_module(module_str, dest_modules) do
    {type, state} =
      case module_str do
        "%" <> _ ->
          {:flipflop, :off}

        "&" <> _ ->
          {:conjunction, nil}

        "broadcaster" ->
          {:broadcaster, nil}
          # _ -> {:simple, nil}
      end

    %{
      type: type,
      state: state,
      src: [],
      src_value: [],
      dest: dest_modules
    }
  end

  def read_rule(text) do
    [module_str, dest_modules_str] =
      text
      |> String.replace(" ", "")
      |> String.split("->")

    dest_modules = String.split(dest_modules_str, ",")
    module = Sol20.make_module(module_str, dest_modules)
    module_name = Regex.replace(~r/[%&]/, module_str, "")
    {module_name, module}
  end

  def update_src(module_name, modules) do
    IO.inspect({"update_src", module_name, modules[module_name]})

    Enum.reduce(modules[module_name][:dest], modules, fn dest, acc ->
      dest_module = modules[dest]

      case dest_module do
        nil ->
          acc

        _ ->
          update_dest_module = Map.put(dest_module, :src, [module_name | dest_module[:src]])

          update_dest_module =
            Map.put(update_dest_module, :src_value, [:low | update_dest_module[:src_value]])

          Map.put(acc, dest, update_dest_module)
      end
    end)
  end

  def read(text) do
    modules =
      String.split(text, "\n")
      |> Enum.map(&amp;Sol20.read_rule(&amp;1))
      |> Enum.into(%{})

    Map.keys(modules)
    |> Enum.reduce(modules, fn module_name, acc -> Sol20.update_src(module_name, acc) end)
  end

  def simulate(machine, [], high_count, low_count) do
    {machine, high_count, low_count}
  end

  def simulate(
        machine,
        [{value, module_name, from_module_name} | pulses_tail],
        high_count,
        low_count
      ) do
    # IO.inspect({from_module_name, value, module_name, high_count, low_count})
    new_high_count = high_count + if value == :high, do: 1, else: 0
    new_low_count = low_count + if value == :low, do: 1, else: 0
    module = machine[module_name]

    {new_machine, new_pulses} =
      case module[:type] do
        nil ->
          {machine, []}

        :broadcaster ->
          {machine, Enum.map(module[:dest], fn x -> {value, x, module_name} end)}

        :flipflop ->
          case {module[:state], value} do
            {_, :high} ->
              {machine, []}

            {:on, :low} ->
              new_machine = Map.put(machine, module_name, Map.put(module, :state, :off))
              new_pulses = Enum.map(module[:dest], fn x -> {:low, x, module_name} end)
              {new_machine, new_pulses}

            {:off, :low} ->
              new_machine = Map.put(machine, module_name, Map.put(module, :state, :on))
              new_pulses = Enum.map(module[:dest], fn x -> {:high, x, module_name} end)
              {new_machine, new_pulses}
          end

        :conjunction ->
          idx = Enum.find_index(module[:src], &amp;(&amp;1 == from_module_name))

          new_module =
            Map.put(module, :src_value, List.replace_at(module[:src_value], idx, value))

          out_signal =
            case Enum.all?(new_module[:src_value], fn x -> x == :high end) do
              true -> :low
              false -> :high
            end

          new_machine = Map.put(machine, module_name, new_module)
          new_pulses = Enum.map(module[:dest], fn x -> {out_signal, x, module_name} end)
          {new_machine, new_pulses}
      end

    Sol20.simulate(new_machine, pulses_tail ++ new_pulses, new_high_count, new_low_count)
  end
end

input_data = Kino.Input.read(input)
machine = Sol20.read(input_data)

pulses = [{:low, "broadcaster", "button"}]
# Sol20.simulate(machine, pulses, 0, 0)
{_, high_count, low_count} =
  Enum.reduce(1..1000, {machine, 0, 0}, fn _, {mac, high_count, low_count} ->
    Sol20.simulate(mac, pulses, high_count, low_count)
  end)

high_count * low_count
m = %{a: [1, 2, 3]}
update_in(m, [:a], &amp;(&amp;1 ++ [4]))