Day 1
Mix.install([
{:kino_aoc, "~> 0.1"}
])
Input
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2023", "1", System.fetch_env!("LB_AOC_SESSION"))
Parser
defmodule Parser do
@regex1 ~r/\d/
# credit https://github.com/Lakret/aoc2023/blob/main/d01.livemd
@regex2 ~r/(?(\d))|(?(one)|(two)|(three)|(four)|(five)|(six)|(seven)|(eight)|(nine))/
@num_dict %{
"one" => "1",
"two" => "2",
"three" => "3",
"four" => "4",
"five" => "5",
"six" => "6",
"seven" => "7",
"eight" => "8",
"nine" => "9"
}
def parse(input) do
nums =
Regex.scan(@regex1, input)
|> List.flatten()
(List.first(nums) <> List.last(nums))
|> String.to_integer()
end
def parse_two(input) do
matches =
scan_with_overlaps(input, [])
|> Enum.map(&to_digit/1)
(hd(matches) <> List.last(matches))
|> String.to_integer()
end
defp scan_with_overlaps(input, []) when is_binary(input) do
scan_with_overlaps(
input,
Regex.run(
@regex2,
input,
return: :index,
capture: :first
),
[]
)
end
defp scan_with_overlaps(input, [{index, length}], matches) do
scan_with_overlaps(
input,
Regex.run(
@regex2,
input,
return: :index,
capture: :first,
offset: index + 1
),
[String.slice(input, index, length) | matches]
)
end
defp scan_with_overlaps(_input, nil, matches), do: Enum.reverse(matches)
defp to_digit(digit) do
Map.get(@num_dict, digit, digit)
end
end
Test Parser
ExUnit.start(autorun: false)
defmodule ParserTest do
use ExUnit.Case, async: true
import Parser
@input "1abc2"
@expected 12
test "parse test" do
actual = parse(@input)
assert actual == @expected
end
end
ExUnit.run()
Part 1
defmodule PartOne do
def solve(input) do
IO.puts("--- Part One ---")
IO.puts("Result: #{run(input)}")
end
def run(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(&String.trim/1)
|> Enum.map(&Parser.parse/1)
|> Enum.sum()
end
end
Test Part 1
ExUnit.start(autorun: false)
defmodule PartOneTest do
use ExUnit.Case, async: true
import PartOne
@input puzzle_input
@expected 55477
test "part one" do
actual = run(@input)
assert actual == @expected
end
end
ExUnit.run()
PartOne.solve(puzzle_input)
Part 2
defmodule PartTwo do
def solve(input) do
IO.puts("--- Part One ---")
IO.puts("Result: #{run(input)}")
end
def run(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(&String.trim/1)
|> Enum.map(&Parser.parse_two/1)
|> Enum.sum()
end
end
Test Part 2
ExUnit.start(autorun: false)
defmodule PartTwoTest do
use ExUnit.Case, async: true
import PartTwo
@input puzzle_input
@expected 54431
test "part two" do
actual = run(@input)
assert actual == @expected
end
end
ExUnit.run()