Day 3: Binary Diagnostic
Setup
defmodule Setup do
def get_input(prompt) do
case IO.gets(prompt) do
:eof -> ""
line -> line <> get_input(prompt)
end
end
def parse_input(report) do
report
|> String.split("\n", trim: true)
|> Enum.map(&String.codepoints/1)
end
def parse_sorted(report) do
report
|> String.split("\n", trim: true)
|> Enum.sort()
|> Enum.map(&String.codepoints/1)
end
end
report = Setup.get_input("input")
input = report |> Setup.parse_input()
sorted_input = report |> Setup.parse_sorted()
:ok
defmodule Diagnostics do
def transpose(matrix) do
matrix
|> Enum.zip()
|> Enum.map(&Tuple.to_list/1)
end
def most_common_bits(readings) do
readings
|> transpose()
|> Enum.map(&Enum.frequencies/1)
|> Enum.map(fn
%{"0" => zeros, "1" => ones} when ones >= zeros -> "1"
_ -> "0"
end)
end
def flip(bits) do
Enum.map(bits, fn
"1" -> "0"
"0" -> "1"
end)
end
def gamma(most_common_bits) do
most_common_bits
|> Enum.join()
|> String.to_integer(2)
end
def epsilon(most_common_bits) do
most_common_bits
|> flip()
|> Enum.join()
|> String.to_integer(2)
end
def power_consumption(readings) do
mcb = most_common_bits(readings)
gamma(mcb) * epsilon(mcb)
end
def oxygen_rating(readings) do
rating(readings, &Kernel.==/2)
end
def co2_rating(readings) do
rating(readings, &Kernel.!=/2)
end
defp rating(readings, fun) do
size = readings |> List.first() |> Enum.count()
Enum.reduce_while(0..(size - 1), readings, fn
_, [reading] ->
{:halt, [reading]}
index, readings ->
most_common_bit = most_common_bits(readings) |> Enum.at(index)
{:cont, Enum.filter(readings, &fun.(Enum.at(&1, index), most_common_bit))}
end)
|> List.first()
|> Enum.join()
|> String.to_integer(2)
end
def life_support_rating(readings) do
oxygen_rating(readings) * co2_rating(readings)
end
end
Part1
Diagnostics.power_consumption(input)
Part2
Diagnostics.life_support_rating(input)
SortedSolve
defmodule SortedSolver do
def solve(input) do
oxygen(input) * co2(input)
end
def oxygen(input) do
rating(input, &most_commons/1)
end
def co2(input) do
rating(input, &least_commons/1)
end
defp rating(input, fun) do
width = input |> hd() |> length()
[rating] =
Enum.reduce_while(0..(width - 1), input, fn
_, [readings] ->
{:halt, [readings]}
i, readings ->
{:cont, readings |> take_while_at(i) |> fun.()}
end)
rating
|> Enum.join()
|> String.to_integer(2)
end
defp most_commons({zeros, ones}) when length(ones) >= length(zeros), do: ones
defp most_commons({zeros, _}), do: zeros
defp least_commons({zeros, ones}) when length(ones) >= length(zeros), do: zeros
defp least_commons({_, ones}), do: ones
def take_while_at(input, index) do
Enum.split_while(input, &(Enum.at(&1, index) == "0"))
end
end
SortedSolver.solve(sorted_input)
nums =
sorted_input
|> Diagnostics.transpose()
|> Enum.map(fn r -> Enum.join(r) |> String.to_integer(2) end)
num = hd(nums)
Enum.map(0..1000, fn n ->
round(:math.pow(2, n))
end)
defmodule IndexSolver do
def solve(input) do
oxygen(input) * co2(input)
end
def oxygen(input) do
rating(input, &update_mc_boundaries/2)
end
def co2(input) do
rating(input, &update_lc_boundaries/2)
end
def rating(input, fun) do
{i, _} =
input
|> Diagnostics.transpose()
|> Enum.reduce_while({0, length(input) - 1}, fn
col, {upper, lower} when lower - upper > 1 ->
{:cont,
col
|> Enum.slice(upper..lower)
|> Enum.find_index(&(&1 == "1"))
|> fun.({upper, lower})}
_col, boundaries ->
{:halt, boundaries}
end)
input
|> Enum.at(i)
|> Enum.join()
|> String.to_integer(2)
end
defp update_mc_boundaries(index, {upper, lower}) when index > div(lower - upper, 2),
do: {upper, upper + index}
defp update_mc_boundaries(index, {upper, lower}), do: {upper + index, lower}
defp update_lc_boundaries(index, {upper, lower}) when index > div(lower - upper, 2),
do: {upper + index, lower}
defp update_lc_boundaries(index, {upper, _lower}), do: {upper, upper + index}
end
Enum.at(sorted_input, 368)
Tests
ExUnit.start()
defmodule Test do
use ExUnit.Case
@test_input """
00100
11110
10110
10111
10101
01111
00111
11100
10000
11001
00010
01010
"""
setup do
{:ok, input: Setup.parse_input(@test_input), sorted_input: Setup.parse_sorted(@test_input)}
end
test "parse input", %{input: input} do
assert input == [
["0", "0", "1", "0", "0"],
["1", "1", "1", "1", "0"],
["1", "0", "1", "1", "0"],
["1", "0", "1", "1", "1"],
["1", "0", "1", "0", "1"],
["0", "1", "1", "1", "1"],
["0", "0", "1", "1", "1"],
["1", "1", "1", "0", "0"],
["1", "0", "0", "0", "0"],
["1", "1", "0", "0", "1"],
["0", "0", "0", "1", "0"],
["0", "1", "0", "1", "0"]
]
end
test "diagnostics", %{input: input} do
mcb = Diagnostics.most_common_bits(input)
assert Diagnostics.gamma(mcb) == 22
assert Diagnostics.epsilon(mcb) == 9
assert Diagnostics.power_consumption(input) == 198
end
test "life support rating", %{input: input} do
assert Diagnostics.oxygen_rating(input) == 23
assert Diagnostics.co2_rating(input) == 10
assert Diagnostics.life_support_rating(input) == 230
end
test "SortedSolver", %{sorted_input: input} do
assert SortedSolver.solve(input) == 230
end
test "IndexSolver", %{sorted_input: input} do
assert IndexSolver.oxygen(input) == 23
assert IndexSolver.co2(input) == 10
assert IndexSolver.solve(input) == 230
end
end
ExUnit.run()
Mix.install([:benchee])
Benchee.run(%{
"Diagnostics" => fn -> Diagnostics.life_support_rating(input) end,
"SortedSolve" => fn -> SortedSolver.solve(sorted_input) end,
"IndexSolver" => fn -> IndexSolver.solve(sorted_input) end
})
:ok