Day 2: Red-Nosed Reports
Mix.install([
{:kino, "~> 0.14.0"}
])
Reading
- We’re analyzing data readings from a reactor
-
Input: One report per line
- Each unmber in a report is called a level
-
Determine which are “save”
- all levels are increasing or decreasing
- adjacent levels differ by at least one and at most three
Input
File day2_input.bin
in the attachments.
Loading the Data
reports =
"day2_input.bin"
|> Kino.FS.file_path()
|> File.stream!()
|> Stream.map(fn line ->
line
|> String.split()
|> Enum.map(&String.to_integer/1)
end)
|> Enum.to_list()
NOTE: Elixir interprets some of the lists with integers as strings. This is why in the printed output they show up as weird strings. They’re all lists of numbers though!
all_same_elements? = fn [first | rest] ->
Enum.all?(rest, fn element -> element == first end)
end
is_safe? = fn report ->
chunks = Enum.chunk_every(report, 2, 1, :discard)
one_direction? = chunks
|> Enum.map(fn [a, b] -> a > b end)
|> all_same_elements?.()
step_size? = Enum.reduce_while(chunks, true, fn [a, b], _acc ->
difference = abs(a - b)
if difference > 3 or difference < 1, do: {:halt, false}, else: {:cont, true}
end)
one_direction? and step_size?
end
safe_count = Enum.reduce(reports, 0, fn report, count ->
if is_safe?.(report), do: count + 1, else: count
end)
Part 2
- We want to tolerate some level of unsafety
- If a report only contains a single bad level, it is still considered safe
Brute force would be to remove one level left-to-right from an unsafe the report and run the safety check again. If either of them succeed, we count it safe. If not, it stays unsafe.
dampened_safe_count = Enum.reduce(reports, 0, fn report, count ->
if is_safe?.(report) do
count + 1
else
Enum.reduce_while(1..length(report), count, fn index, _ ->
report
|> List.delete_at(index - 1)
|> is_safe?.()
|> case do
true -> {:halt, count + 1}
false -> {:cont, count}
end
end)
end
end)
Alternatively, we can make is_safe/1
recursive. It will receive the current status of our evaluation until now and the rest of the levels we still need to look at for this report.
# PSEUDO CODE!
recursive_is_safe? = fn {already_skipped?, direction, previous}, [level | rest] ->
# TODO determine initial `direction` for first two items
step_size? = abs(previous - level) # TODO check if valid jump
one_direction? = same_direction(previous, level) # TODO implement using `:gte`, etz?
case do
step_size? and one_direction? ->
recursive_is_safe?.({already_skipped?, direction, level}, rest)
already_skipped? == false ->
# Drop current level and try to continue
recursive_is_safe?.({true, direction, previous}, rest)
already_skipped? == true ->
# We already dropped once before, no use
false
end
end