Powered by AppSignal & Oban Pro

Day 3

2021/notebooks/day-03.livemd

Day 3

Setup

input = Aoc.get_input(3)
textarea = Kino.Input.textarea("Puzzle input", default: input)
lines = Kino.Input.read(textarea) |> String.split("\n", trim: true)

Part 1

lines
|> Enum.reduce(%{}, fn line, acc ->
  line
  |> String.split("", trim: true)
  |> Enum.with_index()
  |> Enum.reduce(acc, fn
    {"0", i}, acc -> Map.update(acc, i, {1, 0}, fn {zeroes, ones} -> {zeroes + 1, ones} end)
    {"1", i}, acc -> Map.update(acc, i, {0, 1}, fn {zeroes, ones} -> {zeroes, ones + 1} end)
  end)
end)
|> Enum.reduce({"", ""}, fn {_, {zeroes, ones}}, {gamma, epsilon} ->
  if zeroes > ones do
    {gamma <> "0", epsilon <> "1"}
  else
    {gamma <> "1", epsilon <> "0"}
  end
end)
|> then(fn {gamma, epsilon} ->
  {gamma, ""} = Integer.parse(gamma, 2)
  {epsilon, ""} = Integer.parse(epsilon, 2)
  {gamma, epsilon}
end)
|> Tuple.product()

Part 2

bits =
  lines
  |> Enum.at(0)
  |> String.length()
  |> IO.inspect(label: "bits")

oxygen =
  0..bits
  |> Enum.reduce_while(lines, fn i, lines ->
    lines
    |> Enum.group_by(&String.at(&1, i))
    |> then(fn group ->
      zeroes = Map.get(group, "0") |> Enum.count()
      ones = Map.get(group, "1") |> Enum.count()

      if zeroes > ones do
        Map.get(group, "0")
      else
        Map.get(group, "1")
      end
    end)
    |> case do
      [last] -> {:halt, last}
      lines -> {:cont, lines}
    end
  end)
  |> Integer.parse(2)
  |> elem(0)
  |> IO.inspect(label: "oxygen")

co2 =
  0..bits
  |> Enum.reduce_while(lines, fn i, lines ->
    lines
    |> Enum.group_by(&String.at(&1, i))
    |> then(fn group ->
      zeroes = Map.get(group, "0") |> Enum.count()
      ones = Map.get(group, "1") |> Enum.count()

      if zeroes > ones do
        Map.get(group, "1")
      else
        Map.get(group, "0")
      end
    end)
    |> case do
      [last] -> {:halt, last}
      lines -> {:cont, lines}
    end
  end)
  |> Integer.parse(2)
  |> elem(0)
  |> IO.inspect(label: "co2")

oxygen * co2

Part 1 revisited

A little bit less noise in the pipeline

lines
|> Enum.map(&String.split(&1, "", trim: true))
|> Enum.zip()
|> Enum.map(&Tuple.to_list/1)
|> Enum.map(&Enum.frequencies(&1))
|> Enum.map(&Enum.max_by(&1, fn {_, v} -> v end))
|> Enum.map(&elem(&1, 0))
|> Enum.reduce([0, 0], fn most_freq, [gamma, epsilon] ->
  case most_freq do
    "0" -> [2 * gamma + 1, 2 * epsilon]
    "1" -> [2 * gamma, 2 * epsilon + 1]
  end
end)
|> Enum.product()

Part 2 revisited

Collapse into a nicer pipeline.

Enum.reduce_while is used in place of recursion.

One of the tricky bits is passing &>/2 and &<=/2 to the Enum.max_by functions.

I was originally passing Enum.max_by and Enum.min_by but they were both sorting with &>=/2 and &<=/2, which does not split the groups properly.

bits =
  lines
  |> Enum.at(0)
  |> String.length()
  |> IO.inspect(label: "bits")

[&>/2, &<=/2]
|> Enum.map(fn sorter ->
  0..bits
  |> Enum.reduce_while(lines, fn i, lines ->
    lines
    |> Enum.group_by(&String.at(&1, i))
    |> Enum.max_by(fn {_k, v} -> Enum.count(v) end, sorter)
    |> elem(1)
    |> case do
      [last] -> {:halt, last}
      lines -> {:cont, lines}
    end
  end)
  |> Integer.parse(2)
  |> elem(0)
end)
|> IO.inspect()
|> Enum.product()