Andrew Fontaine

Mostly code

Advent of Code 2019: Day 8

09 Dec 2019
Code Snippet for Advent of Code 2019: Day 8

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 Streams 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 0s, 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!

BIOS password

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.

I ❤ feedback. Let me know what you think of this article on Twitter @afontaine_ca