Mix.install([
{:finch, "~> 0.12.0"}
])
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"
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.
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))
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.
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))