Powered by AppSignal & Oban Pro

Advent of Code 2021 - Day 3

2021/day03.livemd

Advent of Code 2021 - Day 3

Utils

defmodule Utils do
  def read_textarea(name) do
    Stream.iterate("", fn _ -> IO.gets(name) end)
    |> Stream.take_while(&(&1 != :eof))
    |> Enum.join("\r\n")
  end
end
{:module, Utils, <<70, 79, 82, 49, 0, 0, 7, ...>>, {:read_textarea, 1}}

Part 1

defmodule Day3 do
  def solve(input) do
    bit_frequencies =
      input
      |> String.split(~r{\s}, trim: true)
      |> Enum.map(&amp;String.to_charlist/1)
      |> Enum.zip()
      |> Enum.map(&amp;Tuple.to_list/1)
      |> Enum.map(&amp;Enum.frequencies/1)

    gamma_rate =
      bit_frequencies
      |> Enum.map(&amp;Enum.max_by(&amp;1, fn {_bit, count} -> count end))
      |> Enum.map(fn {bit, _count} -> bit end)
      |> to_string()
      |> String.to_integer(2)

    epsilon_rate =
      bit_frequencies
      # same as gamma_rate, but -count instead of count
      |> Enum.map(&amp;Enum.max_by(&amp;1, fn {_bit, count} -> -count end))
      |> Enum.map(fn {bit, _count} -> bit end)
      |> to_string()
      |> String.to_integer(2)

    gamma_rate * epsilon_rate
  end
end

Day3.solve(Utils.read_textarea("example_input"))
Day3.solve(Utils.read_textarea("my_input"))
1131506
defmodule Diagnostics do
  def power_consumption(report) do
    bit_frequencies =
      report
      |> to_bitlists()
      |> to_bit_frequencies()

    gamma_rate = gamma_rate(bit_frequencies)
    epsilon_rate = epsilon_rate(bit_frequencies)

    gamma_rate * epsilon_rate
  end

  def gamma_rate(bit_frequencies) do
    picker_fn = fn freq0, freq1 ->
      if freq0 > freq1, do: ?0, else: ?1
    end

    rate(bit_frequencies, picker_fn)
  end

  def epsilon_rate(bit_frequencies) do
    picker_fn = fn freq0, freq1 ->
      if freq0 < freq1, do: ?0, else: ?1
    end

    rate(bit_frequencies, picker_fn)
  end

  defp rate(bit_frequencies, picker_fn) do
    bit_frequencies
    |> Enum.map(fn freqs ->
      freq0 = Map.get(freqs, ?0, 0)
      freq1 = Map.get(freqs, ?1, 0)
      picker_fn.(freq0, freq1)
    end)
    |> to_string()
    |> String.to_integer(2)
  end

  def to_bitlists(report) do
    report
    |> String.split(~r{\s}, trim: true)
    |> Enum.map(&amp;String.to_charlist/1)
  end

  defp to_bit_frequencies(bitlists) do
    bitlists
    |> Enum.zip()
    |> Enum.map(&amp;Tuple.to_list/1)
    |> Enum.map(&amp;Enum.frequencies/1)
  end
end

Diagnostics.power_consumption(Utils.read_textarea("example_input"))
Diagnostics.power_consumption(Utils.read_textarea("my_input"))
1131506

Part2

defmodule Diagnostics.Part2 do
  def life_support_rating(report) do
    bitlists = Diagnostics.to_bitlists(report)

    oxygen_generator_rating = oxygen_generator_rating(bitlists)
    co2_scrubber_rating = co2_scrubber_rating(bitlists)

    oxygen_generator_rating * co2_scrubber_rating
  end

  def oxygen_generator_rating(bitlists) do
    filter_fn = fn bit, freq0, freq1 ->
      if bit == ?1 do
        freq1 >= freq0
      else
        freq0 > freq1
      end
    end

    rating(bitlists, filter_fn, 0)
  end

  def co2_scrubber_rating(bitlists) do
    filter_fn = fn bit, freq0, freq1 ->
      if bit == ?0 do
        freq0 <= freq1
      else
        freq1 < freq0
      end
    end

    rating(bitlists, filter_fn, 0)
  end

  defp rating([bitlist], _filter_fn, _pos) do
    bitlist
    |> to_string()
    |> String.to_integer(2)
  end

  defp rating(bitlists, filter_fn, pos) do
    # calculate frequencies of 0 & 1 at this position
    freqs =
      bitlists
      |> Enum.map(&amp;Enum.at(&amp;1, pos))
      |> Enum.frequencies()

    freq0 = Map.get(freqs, ?0, 0)
    freq1 = Map.get(freqs, ?1, 0)

    new_bitlists =
      bitlists
      |> Enum.filter(fn bitlist ->
        bit = Enum.at(bitlist, pos)
        filter_fn.(bit, freq0, freq1)
      end)

    rating(new_bitlists, filter_fn, pos + 1)
  end
end

Diagnostics.Part2.life_support_rating(Utils.read_textarea("example_input"))
Diagnostics.Part2.life_support_rating(Utils.read_textarea("my_input"))
7863147