Advent of Code (2016) : Day 1

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

  @input "L3, R1, L4, L1, L2, R4, L3, L3, R2, R3, L5, R1, R3, L4, L1, L2, R2, R1, L4, L4, R2, L5, R3, R2, R1, L1, L2, R2, R2, L1, L1, R2, R1, L3, L5, R4, L3, R3, R3, L5, L190, L4, R4, R51, L4, R5, R5, R2, L1, L3, R1, R4, L3, R1, R3, L5, L4, R2, R5, R2, L1, L5, L1, L1, R78, L3, R2, L3, R5, L2, R2, R4, L1, L4, R1, R185, R3, L4, L1, L1, L3, R4, L4, L1, R5, L5, L1, R5, L1, R2, L5, L2, R4, R3, L2, R3, R1, L3, L5, L4, R3, L2, L4, L5, L4, R1, L1, R5, L2, R4, R2, R3, L1, L1, L4, L3, R4, L3, L5, R2, L5, L1, L1, R2, R3, L5, L3, L2, L1, L4, R4, R4, L2, R3, R1, L2, R1, L2, L2, R3, R3, L1, R4, L5, L3, R4, R4, R1, L2, L5, L3, R1, R4, L2, R5, R4, R2, L5, L3, R4, R1, L1, R5, L3, R1, R5, L2, R1, L5, L2, R2, L2, L3, R3, R3, R1"


  ###########
  #         #
  # Part 1  #
  #         #
  ###########


  # reducing a list of instructions to the final state by
  # repeatedly applying new_state on the current instruction and state
  def final_state(instruction_list) do
    {x_final, y_final, _} = Enum.reduce(instruction_list, {0,0,"N"}, &new_state/2)
    {x_final, y_final}
  end

  # returning the distance in blocks between
  # the initial position i.e. (0,0) and the given position
  def block_distance({x,y}) do
    abs(x) + abs(y)
  end

  # the state to consider is {x_coordinate, y_coordinate, direction_facing}
  # every instruction changes this state
  # this function takes the old state and a single instruction and returns the new state
  # after this the problem is reduced to finding the final state
  # given {0,0,"N"} is the initial state, and calculating the distance in blocks
  # which is done by final_state and block_distance respectively

  #           +y
  #            N
  #            ^
  #            |
  #            |
  #
  #-x W<-----+0,0+---->E +x
  #
  #            |
  #            |
  #            v
  #            S
  #           -y
  def new_state(instruction, {x, y, facing}) do
    {direction, steps} = parse_instruction(instruction)
    case {x, y, facing, direction} do
      {x, y, "N", "R"} -> {x + steps, y, "E"}
      {x, y, "N", "L"} -> {x - steps, y, "W"}
      {x, y, "S", "R"} -> {x - steps, y, "W"}
      {x, y, "S", "L"} -> {x + steps, y, "E"}
      {x, y, "W", "R"} -> {x, y + steps, "N"}
      {x, y, "W", "L"} -> {x, y - steps, "S"}
      {x, y, "E", "R"} -> {x, y - steps, "S"}
      {x, y, "E", "L"} -> {x, y + steps, "N"}
    end
  end

  # extract direction and number of steps from an instruction
  def parse_instruction(instruction) do
    case instruction do
      "R" <> steps -> {"R", String.to_integer(steps)}
      "L" <> steps -> {"L", String.to_integer(steps)}
    end
  end

  # convert a string of instructions to a list of instructions
  def parse_input(instruction_string) do
    String.split(instruction_string, ",")
    |> Enum.map(&String.trim/1)
  end


  ###########
  #         #
  # Part 2  #
  #         #
  ###########

  # returns the first location we visited twice
  # for every instruction --
  # 1. calculate the new state
  # 2. calculate the positions visited while transitioning states
  # 3. check if any position in the positions_visited list has been visited before
  # 4. return it on finding such a position
  # 5. otherwise recurse and repeat the procedure for the next instruction, with updated state and position set
  def visited_twice(instruction_list, current_state, position_set) do
    [instruction | tail] = instruction_list
    {x, y, facing} = new_state(instruction, current_state)
    position_list = visited_positions(current_state, {x, y, facing})
    case consume_position_list(position_list, position_set) do
      {:position, position} -> position
      {:set, updated_set} -> visited_twice(tail, {x, y, facing}, updated_set)
    end
  end

  # take a list of positions
  # and a set of already visited positions
  # for every position in the list
  # return the position if it has been visited before
  # otherwise add it to the position set and recurse to cover the rest of the list
  # return the updated set of positions after consuming the entire list and not finding
  # any position visted before
  def consume_position_list([], position_set) do
    {:set, position_set}
  end
  def consume_position_list(position_list, position_set) do
    [position | tail] = position_list
    case position in position_set do
      true -> {:position, position}
      false -> consume_position_list(tail, MapSet.put(position_set, position))
    end
  end

  # all positions visited while transitioning states
  # (0,0) -> (4,0) returns [(1,0),(2,0),(3,0),(4,0)]
  # (0,0) -> (0,4) returns [(0,1),(0,2),(0,3),(0,4)]
  # initial position is not included otherwise
  # it would be added twice once as final position of previous state transition
  # and next time as initial position of current state transition
  # that means position_set has to start off with (0,0) in it
  # to handle the case of a cyclic path
  def visited_positions({x_initial, y_initial, _}, {x_final, y_final, _}) do
    cond do
      x_initial == x_final -> Enum.map(y_initial..y_final, fn(y) -> {x_initial, y} end) |> tl
      y_initial == y_final -> Enum.map(x_initial..x_final, fn(x) -> {x, y_initial} end) |> tl
    end
  end

  def output() do
    instruction_list = parse_input(@input)
    IO.puts("Part-1 solution is: #{final_state(instruction_list) |> block_distance}")
    IO.puts("Part-2 solution is: #{visited_twice(instruction_list, {0, 0, "N"}, MapSet.new([{0,0}])) |> block_distance}")
  end

end

    

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