Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

2016 / Day 4: Security Through Obscurity

2016/day04.livemd

2016 / Day 4: Security Through Obscurity

Puzzle

puzzle = "./puzzles/day04.txt" |> Path.expand(__DIR__) |> File.read!()

rooms =
  puzzle
  |> String.split("\n")
  |> Enum.slice(0..-2)
  |> Enum.map(fn room ->
    captures = Regex.named_captures(~r/(?.*)-(?\d+)\[(?.*)\]/, room)
    {captures["name"], String.to_integer(captures["id"]), captures["checksum"]}
  end)

:ok

Part1

Finally, you come across an information kiosk with a list of rooms. Of course, the list is encrypted and full of decoy data, but the instructions to decode the list are barely hidden nearby. Better remove the decoy data first.

Each room consists of an encrypted name (lowercase letters separated by dashes) followed by a dash, a sector ID, and a checksum in square brackets.

A room is real (not a decoy) if the checksum is the five most common letters in the encrypted name, in order, with ties broken by alphabetization. For example:

  • aaaaa-bbb-z-y-x-123[abxyz] is a real room because the most common letters are a (5), b (3), and then a tie between x, y, and z, which are listed alphabetically.
  • a-b-c-d-e-f-g-h-987[abcde] is a real room because although the letters are all tied (1 of each), the first five are listed alphabetically.
  • not-a-real-room-404[oarel] is a real room.
  • totally-real-room-200[decoy] is not.

Of the real rooms from the list above, the sum of their sector IDs is 1514.

What is the sum of the sector IDs of the real rooms?

defmodule RoomValidator do
  def valid?({name, _id, checksum}) do
    computed_checksum =
      name
      |> letter_occurences()
      |> Enum.sort_by(&{elem(&1, 1), &1 |> elem(0) |> negate_char()}, &>=/2)
      |> Enum.take(5)
      |> Enum.map_join(&elem(&1, 0))

    checksum == computed_checksum
  end

  defp letter_occurences(name) do
    name
    |> String.graphemes()
    |> Enum.filter(fn <> -> c in ?a..?z end)
    |> Enum.reduce(%{}, fn letter, acc ->
      Map.update(acc, letter, 1, &amp;(&amp;1 + 1))
    end)
  end

  defp negate_char(<>), do: ?z - c
end

rooms
|> Enum.filter(&amp;RoomValidator.valid?/1)
|> Enum.map(&amp;elem(&amp;1, 1))
|> Enum.sum()

Part 2

With all the decoy data out of the way, it’s time to decrypt this list and get moving.

The room names are encrypted by a state-of-the-art shift cipher, which is nearly unbreakable without the right software. However, the information kiosk designers at Easter Bunny HQ were not expecting to deal with a master cryptographer like yourself.

To decrypt a room name, rotate each letter forward through the alphabet a number of times equal to the room’s sector ID. A becomes B, B becomes C, Z becomes A, and so on. Dashes become spaces.

For example, the real name for qzmt-zixmtkozy-ivhz-343 is very encrypted name.

What is the sector ID of the room where North Pole objects are stored?

defmodule RoomRotator do
  def run(name, count) do
    name |> String.graphemes() |> Enum.map_join(&amp;rotate(&amp;1, count))
  end

  defp rotate("-", _count), do: " "
  defp rotate(c, 0), do: c

  defp rotate(<>, count) do
    c = rotate_char(c)
    rotate(<>, count - 1)
  end

  defp rotate_char(?z), do: ?a
  defp rotate_char(c), do: c + 1
end

rooms
|> Enum.filter(&amp;RoomValidator.valid?/1)
|> Enum.map(fn {name, id, _checksum} -> {RoomRotator.run(name, id), id} end)
|> Enum.filter(fn {name, _id} -> String.contains?(name, "northpole") end)
|> Enum.at(0)
|> elem(1)