Advent of Code (2016) : Day 4

-- Problem --
You can find Part 1 of the problem statement here. Part 2 is unlocked on successful completion of Part 1.

-- Solution in Elixir --

      defmodule Aoc.Day4 do

  # parse room_data string and return a tuple of parsed attributes
  def parse_room_data(room_data) do
    [[_, encrypted_name, sector_id, checksum]] = Regex.scan(~r{([a-z-]*)-([0-9]*)\[([a-z]*)\]}, room_data)
    {encrypted_name, sector_id, checksum}
  end

  def calculate_checksum(encrypted_name) do
    list_with_dups = String.replace(encrypted_name, "-", "") |> String.graphemes # remove the dashes and parse the characters into a list
    list_without_dups = Enum.uniq(list_with_dups) # remove the duplicates, this acts as set of characters in the list to map on
    list_with_count = Enum.map(list_without_dups,&({count_occurrences(list_with_dups, &1), &1})) # ["a","b","c","c"] => [{1,"a"},{1,"b"},{2,"c"}]
    # we need the count to be in decreasing order but the characters to be in increasing order(normal lexicographic order), hence the -count in sort criteria
    sorted_list_with_count = Enum.sort_by(list_with_count, fn({count, char}) -> {-count, char} end, &<=/2)
    sorted_list_with_count |> Enum.take(5) |> Enum.map_join(fn({count, char}) -> char end) # required checksum
  end

  def parse_input() do
    {:ok, input} = File.read("day4_input.txt")
    String.split(input)
    |> Enum.map(&parse_room_data/1)
  end

  # code point of a, i.e ?a is 97
  # find the shifted character by adding sector_id
  # and mapping to one of 0..25 and then adding back ?a and converting back to binary
  # looks like sorcery, really isn't, trust me
  def decrypt_name(room_data) do
    {encrypted_name, sector_id, _checksum} = room_data
    sector_id = String.to_integer(sector_id)
    String.to_charlist(encrypted_name)
    |> Enum.map_join(fn(char) ->
      case char do
        ?- -> " "
        num when num in ?a..?z -> <>
      end
    end)
  end

  # map over the parsed input list
  # and for every tuple representing a room in the list
  # return sector_id if the tuple represents a real room
  # i.e checksum checks out
  # otherwise return 0 and sum the generated list by map to return the answer for part-1
  def output_1() do
    parse_input()
    |> Enum.map(fn({encrypted_name, sector_id, checksum}) ->
      case checksum == calculate_checksum(encrypted_name) do
        true -> String.to_integer(sector_id)
        false -> 0
      end
    end)
    |> Enum.sum()
  end

  # decrypt all the encrypted names
  # find the one with north in it
  def output_2() do
    parse_input()
    |> Enum.map(fn({encrypted_name, sector_id, _checksum}) ->
      {sector_id, decrypt_name({encrypted_name, sector_id, _checksum})}
    end)
    |> Enum.filter(fn({_, decrypted_name}) -> String.contains?(decrypted_name, "north") end)
  end

  # count number of occurences of target_elem in list
  def count_occurrences(list, target_elem) do
    Enum.reduce(list, 0, fn(elem, count) ->
      case elem == target_elem do
        true -> count + 1
        false -> count
      end
    end)
  end
end

    

You can find all my Advent of Code (2016) solutions here.