Instantly share code, notes, and snippets.

# goofansu/aoc2021-day3.livemd

Last active July 14, 2022 10:02
Show Gist options
• Save goofansu/55374102376197ef4c710c539294b752 to your computer and use it in GitHub Desktop.

# Day 3

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

## Helper functions

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

input_stream_fun = fn url ->
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))`