Saving a mars rover for the elves today! Why do they have to send the password
as an image though? Just send me a text, jeez! Anyway, first step is to check
and make sure the image was fully received. I can use Stream.chunk_every/2
to
turn my list of digits to a list of layers. The only difference is that Stream
versions of Enum
functions return Stream
s that are lazily evaluated instead
of iterating through the list all at once.
defp parse_input() do
input_stream()
|> Enum.at(0)
|> String.graphemes()
|> Stream.map(&String.to_integer/1)
end
defp parse_layers(stream, x, y), do: Stream.chunk_every(stream, x * y)
All that’s left is to find the layer that has the least number of 0
s, and
return the checksum.
def p1() do
layer =
parse_input()
|> parse_layers(25, 6)
|> Enum.min_by(&count(&1, 0))
count(layer, 1) * count(layer, 2)
end
defp count(image, x) do
Enum.count(image, &Kernel.==(&1, x))
end
done!
Puzzle 2
I guess I have to display the image to get the password now. A simple
extension on the original problem. The image format is unnecessarily complex as
well (those pesky elves). The plan is to look at a pixel in each layer, and find
the first pixel that is either 0 (black) or 1 (white), ignoring 2s. I can use
Stream.zip/1
to merge the list of layers to a list of pixels, and
Enum.find/2
to find the first pixel that matches the above.
def p2() do
parse_input()
|> parse_layers(25, 6)
|> Stream.zip()
|> Stream.map(&Tuple.to_list/1)
|> Task.async_stream(fn layer ->
Enum.find(layer, fn pixel -> pixel == 0 or pixel == 1 end)
end)
|> Stream.map(fn {:ok, p} -> p end)
|> parse_image(25)
|> Enum.each(&IO.puts/1)
end
Then, once a single list of pixels is left, all that’s left is to map the image to pixels, chunk the pixels into rows, and display the image!
defp parse_image(layer, x) do
layer
|> Enum.map(fn
1 -> IO.ANSI.format_fragment([:white, "█", :reset])
0 -> IO.ANSI.format_fragment([:black, "█", :reset])
end)
|> Enum.chunk_every(x)
end
IO.ANSI.format_fragment/2
returns a charlist
, a fundamental structure that
is a big part of the speed of I/0 in Elixir and Erlang, and helps to keep
Phoenix’s templates fast. Once each pixel is mapped to a charlist
and
chunked into rows, all that is left is to print each row, with |>
Enum.each(&IO.puts/1)
. Then, the image can be displayed!
That’s done as well!
Conclusion
Normally, I dislike writing visualizations, but between
IO.ANSI.format_fragment
and I/O charlists
, printing out the password in a
readable format was very straightforward. No tests today either, as this was
a nice Sunday puzzle.