Advent of Code 2020 in Elixir - Day 8

Handheld Halting

defmodule Aoc2020.Day8 do
  @moduledoc "Handheld Halting"

  def run(), do: part_2()

  def part_1() do
    parse_input() |> boot()
  end

  def part_2() do
    parse_input()
    |> generate_variations()
    |> Enum.find_value(fn program ->
      case boot(program) do
        {:program_exited, acc} -> acc
        _ -> false
      end
    end)
  end

  def parse_input() do
    File.read!("priv/inputs/2020/day8.txt")
    |> String.trim()
    |> String.split("\n")
    |> Enum.map(fn line ->
      [instruction, arg] = String.split(line, " ", parts: 2)
      {num, _} = Integer.parse(arg)
      {String.to_atom(instruction), num}
    end)
  end

  def boot(program), do: run_program(program, 0, 0, [])

  def run_program(program, pointer, acc, history \\ []) do
    if Enum.member?(history, pointer) do
      {:started_loop, acc}
    else
      case Enum.at(program, pointer) do
        {:nop, _} -> run_program(program, pointer + 1, acc, [pointer | history])
        {:acc, arg} -> run_program(program, pointer + 1, acc + arg, [pointer | history])
        {:jmp, arg} -> run_program(program, pointer + arg, acc, [pointer | history])
        nil -> {:program_exited, acc}
      end
    end
  end

  # part 2
  def generate_variations(program) do
    Enum.map(0..(length(program) - 1), fn split_at ->
      {head, [next = {inst, arg} | rest]} = Enum.split(program, split_at)

      case inst do
        :nop ->
          head ++ [{:jmp, arg} | rest]

        :jmp ->
          head ++ [{:nop, arg} | rest]

        _ ->
          head ++ [next | rest]
      end
    end)
  end
end