Powered by AppSignal & Oban Pro

AOC 2023 - Day 01

aoc2023/day01.livemd

AOC 2023 - Day 01

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

AOC Helper

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2023", "1", System.fetch_env!("LB_AOC_SESSION"))

Part 1

Code

defmodule PartOne do
  defp process_line(line) do
    chars = String.graphemes(line)
    start_num = find_number(chars)
    end_num = chars |> Enum.reverse() |> find_number()

    String.to_integer("#{start_num}#{end_num}")
  end

  defp find_number(chars) do
    Enum.find(chars, fn char ->
      case Integer.parse(char) do
        {value, _} -> value
        :error -> nil
      end
    end)
  end

  def solve(input) do
    IO.puts("--- Part One ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input) do
    input
    |> String.split("\n")
    |> Enum.map(&process_line/1)
    |> Enum.sum()
  end
end

Test

ExUnit.start(autorun: false)

defmodule PartOneTest do
  use ExUnit.Case, async: true
  import PartOne

  @input "1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet"
  @expected 142

  test "part one" do
    actual = run(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Solution

PartOne.solve(puzzle_input)

Part 2

Code

defmodule PartTwo do
  @num_words %{
    "one" => 1,
    "two" => 2,
    "three" => 3,
    "four" => 4,
    "five" => 5,
    "six" => 6,
    "seven" => 7,
    "eight" => 8,
    "nine" => 9
  }

  defp process_line(line) do
    chars = String.graphemes(line)
    start_num = find_number(chars)
    end_num = chars |> Enum.reverse() |> find_number()

    String.to_integer("#{start_num}#{end_num}")
  end

  defp find_number(chars) do
    chars
    |> Enum.with_index()
    |> Enum.find_value(fn {char, index} ->
      case Integer.parse(char) do
        {value, _} -> value
        :error -> chars |> Enum.slice(0..index) |> Enum.join("") |> num_word()
      end
    end)
  end

  defp num_word(word) do
    Enum.find_value(@num_words, fn {key, value} ->
      cond do
        String.ends_with?(word, key) -> value
        word |> String.reverse() |> String.starts_with?(key) -> value
        true -> nil
      end
    end)
  end

  def solve(input) do
    IO.puts("--- Part Two ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input) do
    input
    |> String.split("\n")
    |> Enum.map(&process_line/1)
    |> Enum.sum()
  end
end

Test

ExUnit.start(autorun: false)

defmodule PartTwoTest do
  use ExUnit.Case, async: true
  import PartTwo

  @input "two1nine
eightwothree
abcone2threexyz
xtwone3four
4nineeightseven2
zoneight234
7pqrstsixteen"
  @expected 281

  test "part two" do
    actual = run(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Solution

PartTwo.solve(puzzle_input)