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

Advent of Code 2023

2023/day20.livemd

Advent of Code 2023

Mix.install([
  {:req, "~> 0.3.2"}
])

Day 20

input =
  "https://adventofcode.com/2023/day/20/input"
  |> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
  |> Map.get(:body)
sample = """
broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a
"""
sample2 = """
broadcaster -> a
%a -> inv, con
&inv -> b
%b -> con
&con -> output
"""

Module

defmodule A do
  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&Regex.split(~r/( -> |, )/, &1))
    |> Enum.map(fn [id | targets] ->
      {id, type} =
        case id do
          "broadcaster" -> {id, :broadcaster}
          "%" <> id -> {id, :flip_flop}
          "&" <> id -> {id, :conjunction}
        end

      {id, %{type: type, targets: targets}}
    end)
    |> Enum.into(Map.new())
    |> then(fn modules ->
      caller_map =
        modules
        |> Enum.reduce(Map.new(), fn {id, %{targets: targets}}, caller_map ->
          targets
          |> Enum.reduce(caller_map, fn target, caller_map ->
            Map.update(caller_map, target, [id], &amp;[id | &amp;1])
          end)
        end)

      initial_state =
        modules
        |> Enum.reduce(Map.new(), fn {id, %{type: type}}, state ->
          case type do
            :broadcaster ->
              state

            :flip_flop ->
              Map.put(state, id, :off)

            :conjunction ->
              initial =
                Map.fetch!(caller_map, id)
                |> Enum.map(&amp;{&amp;1, :low})
                |> Enum.into(Map.new())

              Map.put(state, id, initial)
          end
        end)

      {modules, initial_state}
    end)
  end

  def push_button(state, modules) do
    pulses = [{"button", :low, "broadcaster"}]

    1
    |> Stream.iterate(&amp;(&amp;1 + 1))
    |> Enum.reduce_while({pulses, state, %{high: 0, low: 0}}, fn _i, {pulses, state, counts} ->
      case pulses do
        [] ->
          {:halt, {state, counts}}

        [{from_id, pulse, to_id} | rest] ->
          # {from_id, pulse, to_id} |> IO.inspect(label: "#{times} #{i} pulse")
          counts = Map.update!(counts, pulse, &amp;(&amp;1 + 1))

          # receiver
          module = Map.get(modules, to_id, %{type: :unknown})

          # on receive pulse
          {new_pulses, state} =
            case module.type do
              :broadcaster ->
                new_pulses =
                  module.targets
                  |> Enum.map(&amp;{to_id, pulse, &amp;1})

                {new_pulses, state}

              :flip_flop ->
                case pulse do
                  :high ->
                    {[], state}

                  :low ->
                    flip_state = Map.get(state, to_id)

                    {flip_state, pulse} =
                      case flip_state do
                        :off -> {:on, :high}
                        :on -> {:off, :low}
                      end

                    state = Map.put(state, to_id, flip_state)

                    new_pulses =
                      module.targets
                      |> Enum.map(&amp;{to_id, pulse, &amp;1})

                    {new_pulses, state}
                end

              :conjunction ->
                state = update_in(state[to_id], &amp;Map.put(&amp;1, from_id, pulse))

                all_high? =
                  state[to_id]
                  |> Map.values()
                  |> Enum.all?(&amp;(&amp;1 == :high))

                pulse = if all_high?, do: :low, else: :high

                new_pulses =
                  module.targets
                  |> Enum.map(&amp;{to_id, pulse, &amp;1})

                {new_pulses, state}

              :unknown ->
                {[], state}

              _ ->
                IO.puts("unhandled #{module.type}")
                {[], state}
            end

          {:cont, {rest ++ new_pulses, state, counts}}
      end
    end)
  end
end
import A

Part 1

[sample, sample2, input]
|> Enum.map(fn input ->
  input
  |> parse()
  |> then(fn {modules, initial_state} ->
    1..1000
    |> Enum.reduce({initial_state, {0, 0}}, fn _i, {state, {high, low}} ->
      {state, counts} = push_button(state, modules)
      {state, {high + counts.high, low + counts.low}}
    end)
    |> elem(1)
    |> Tuple.to_list()
    |> Enum.product()
  end)
end)

Part 2

[
  sample,
  # sample2,
  # input
]
|> Enum.map(fn input ->
  input
  |> parse()
end)