Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Day 3

Mix.install([
  {:finch, "~> 0.12.0"}
])

Helper functions

Finch.start_link(name: MyFinch)
download_fun = fn
  {:status, 200}, acc -> acc
  {:headers, _}, acc -> acc
  {:data, data}, acc -> acc <> data
end

input_stream_fun = fn url ->
  {:ok, data} = Finch.build(:get, url) |> Finch.stream(MyFinch, "", download_fun)
  String.splitter(data, "\n", trim: true)
end
input_url =
  "https://gist.githubusercontent.com/goofansu/36c789d1729a3c0d4934e6c8f1f8e630/raw/2ccdefc196318692034b4e92ddab87c62d944cd2/gistfile1.txt"

Part 1

What is the power consumption of the submarine?

  • Power consumption = gamma rate * epsilon rate
  • gamma rate = most common bits -> decimal
  • epsilon rate = least common bits -> decimal

For example:

00100
11110
10110
10111
10101
01111
00111
11100
10000
11001
00010
01010

Considering only the first bit of each number, there are five 0 bits and seven 1 bits. Since the most common bit is 1, the first bit of the gamma rate is 1.

Solution

byte_size("00100")
<<b1::8, b2::8, b3::8, b4::8, b5::8>> = "00100"
String.split("00100", "", trim: true)
|> Enum.with_index()
|> Enum.reduce([], fn
  {"0", index}, acc -> List.update_at(acc, index, &(&1 + 1))
  _, acc -> acc
end)
# Build a n elements list to accumulate the count of 0.

reducer = fn bits, acc ->
  String.split(bits, "", trim: true)
  |> Enum.with_index()
  |> Enum.reduce(acc, fn
    {"0", index}, acc -> List.update_at(acc, index, &(&1 + 1))
    _, acc -> acc
  end)
end

lines = input_stream_fun.(input_url) |> Enum.to_list() |> IO.inspect()
count = Enum.count(lines)

zero_counters = Enum.reduce(lines, :lists.duplicate(12, 0), reducer)
one_counters = Enum.map(zero_counters, &(count - &1))
zero_one_counters = Enum.zip(zero_counters, one_counters)

gamma_rate =
  Enum.map(zero_one_counters, fn
    {zero, one} when zero < one -> 1
    {zero, one} when zero > one -> 0
  end)
  |> IO.inspect(label: "gamma rate")

epsilon_rate =
  Enum.map(zero_one_counters, fn
    {zero, one} when zero < one -> 0
    {zero, one} when zero > one -> 1
  end)
  |> IO.inspect(label: "epsilon rate")

<<gamma_rate::12>> = for i <- gamma_rate, do: <<i::1>>, into: <<>>
<<epsilon_rate::12>> = for i <- epsilon_rate, do: <<i::1>>, into: <<>>
gamma_rate * epsilon_rate
defmodule Day3 do
  def part1(stream) do
    {most, least} = most_least_bits(stream)
    gamma_rate = to_decimal(most)
    epsilon_rate = to_decimal(least)
    gamma_rate * epsilon_rate
  end

  defp most_least_bits(stream) do
    stream
    |> Stream.map(&String.split(&1, "", trim: true))
    |> Stream.flat_map(&Enum.with_index/1)
    |> Enum.reduce(%{}, fn element, acc -> Map.update(acc, element, 1, &(&1 + 1)) end)
    |> Enum.reduce({%{}, %{}}, &reducer/2)
  end

  defp reducer({{bit, index}, count}, {most, least}) do
    most =
      case Map.get(most, index) do
        nil ->
          Map.put(most, index, {bit, count})

        {existing_bit, existing_count} when existing_count > count ->
          Map.put(most, index, existing_bit)

        {_, existing_count} when existing_count < count ->
          Map.put(most, index, bit)
      end

    least =
      case Map.get(least, index) do
        nil ->
          Map.put(least, index, {bit, count})

        {_, existing_count} when existing_count > count ->
          Map.put(least, index, bit)

        {existing_bit, existing_count} when existing_count < count ->
          Map.put(least, index, existing_bit)
      end

    {most, least}
  end

  defp to_decimal(index_bit_map) do
    index_bit_map
    |> Enum.sort_by(&elem(&1, 0))
    |> Enum.map(&elem(&1, 1))
    |> List.to_charlist()
    |> List.to_integer(2)
  end
end
str = """
00100
11110
10110
10111
10101
01111
00111
11100
10000
11001
00010
01010
"""

Day3.part1(String.splitter(str, "\n", trim: true))
Day3.part1(input_stream_fun.(input_url))

Part 2

What is the life support rating of the submarine?

  • life support rating = oxygen generator rating * CO2 scrubber rating
  • To find oxygen generator rating, determine the most common value (0 or 1) in the current bit position, and keep only numbers with that bit in that position. If 0 and 1 are equally common, keep values with a 1 in the position being considered.
  • To find CO2 scrubber rating, determine the least common value (0 or 1) in the current bit position, and keep only numbers with that bit in that position. If 0 and 1 are equally common, keep values with a 0 in the position being considered.

Solution

str = """
00100
11110
10110
10111
10101
01111
00111
11100
10000
11001
00010
01010
"""

lines = String.split(str, "\n", trim: true)

{zeros, ones} =
  Enum.reduce(lines, {0, 0}, fn line, {zeros, ones} ->
    case String.slice(line, 0, 1) do
      "0" -> {zeros + 1, ones}
      "1" -> {zeros, ones + 1}
    end
  end)

most_bit = if ones >= zeros, do: "1", else: "0"

Enum.filter(lines, fn line -> String.slice(line, 0, 1) == most_bit end)
defmodule Day3 do
  def part2(input_stream) do
    o2_rating = rating(input_stream, fn ones, zeros -> if ones >= zeros, do: "1", else: "0" end)
    co2_rating = rating(input_stream, fn ones, zeros -> if zeros <= ones, do: "0", else: "1" end)
    o2_rating * co2_rating
  end

  defp rating(input_stream, bit_criteria_fun) do
    Stream.iterate(0, &(&1 + 1))
    |> Stream.transform(input_stream, fn index, acc ->
      {zeros, ones} =
        Enum.reduce(acc, {0, 0}, fn line, {zeros, ones} ->
          case String.slice(line, index, 1) do
            "0" -> {zeros + 1, ones}
            "1" -> {zeros, ones + 1}
          end
        end)

      expected_bit = bit_criteria_fun.(ones, zeros)

      case Enum.filter(acc, fn line -> String.slice(line, index, 1) == expected_bit end) do
        [] -> {:halt, []}
        [elem] -> {[elem], []}
        filtered -> {[], filtered}
      end
    end)
    |> Enum.at(0)
    |> String.to_integer(2)
  end
end
Day3.part2(input_stream_fun.(input_url))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment