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
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.