refactor: Move Day 1 logic into dedicated module

refactor: Limit public API of Day1 module and remove private function tests
This commit is contained in:
Ruidy (aider) 2024-12-01 16:15:19 +01:00 committed by Ruidy
parent bbf027d22a
commit 992008b34c
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
3 changed files with 57 additions and 122 deletions

View file

@ -3,89 +3,6 @@ defmodule AdventCode2024 do
Documentation for `AdventCode2024`.
"""
@doc """
Reads the input file for day 1 and calculates the total distance between the two lists.
Returns {:ok, result} if successful, {:error, reason} if there's an error.
"""
def solve_day1(input_file \\ "day1/input.txt") do
case File.read(input_file) do
{:ok, content} ->
{left_list, right_list} = parse_input(content)
result = calculate_total_distance(left_list, right_list)
{:ok, result}
{:error, reason} -> {:error, reason}
end
end
@doc """
Reads the input file for day 1 part 2 and calculates the similarity score between the two lists.
Returns {:ok, result} if successful, {:error, reason} if there's an error.
"""
def solve_day1_part2(input_file \\ "day1/input.txt") do
case File.read(input_file) do
{:ok, content} ->
{left_list, right_list} = parse_input(content)
result = calculate_similarity_score(left_list, right_list)
{:ok, result}
{:error, reason} -> {:error, reason}
end
end
@doc """
Parses the input string into two lists of numbers.
Input format is expected to be tab-separated numbers, one pair per line.
"""
def parse_input(content) do
{left, right} =
content
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, ~r/\s+/, trim: true))
|> Enum.map(fn [left, right] ->
{String.to_integer(left), String.to_integer(right)}
end)
|> Enum.unzip()
{left, right}
end
@doc """
Calculates the total distance between two lists of numbers.
Lists are first sorted, then paired up, and the absolute differences are summed.
## Examples
iex> AdventCode2024.calculate_total_distance([3, 4, 2, 1, 3, 3], [4, 3, 5, 3, 9, 3])
11
iex> AdventCode2024.calculate_total_distance([], [])
0
iex> AdventCode2024.calculate_total_distance([1], [3])
2
"""
def calculate_total_distance(left_list, right_list) do
Enum.zip(Enum.sort(left_list), Enum.sort(right_list))
|> Enum.map(fn {a, b} -> abs(a - b) end)
|> Enum.sum()
end
@doc """
Calculates the similarity score between two lists.
For each number in the left list, multiplies it by the number of times it appears in the right list.
Returns the sum of all these products.
## Examples
iex> AdventCode2024.calculate_similarity_score([3, 4, 2, 1, 3, 3], [4, 3, 5, 3, 9, 3])
31
"""
def calculate_similarity_score(left_list, right_list) do
frequencies = Enum.frequencies(right_list)
left_list
|> Enum.map(fn num ->
num * Map.get(frequencies, num, 0)
end)
|> Enum.sum()
end
defdelegate solve_day1(input_file \\ "day1/input.txt"), to: AdventCode2024.Day1, as: :solve
defdelegate solve_day1_part2(input_file \\ "day1/input.txt"), to: AdventCode2024.Day1, as: :solve_part2
end

View file

@ -0,0 +1,54 @@
defmodule AdventCode2024.Day1 do
@moduledoc """
Solution for Advent of Code 2024 - Day 1: Historian Hysteria
"""
def solve(input_file \\ "day1/input.txt") do
case File.read(input_file) do
{:ok, content} ->
{left_list, right_list} = parse_input(content)
result = calculate_total_distance(left_list, right_list)
{:ok, result}
{:error, reason} -> {:error, reason}
end
end
def solve_part2(input_file \\ "day1/input.txt") do
case File.read(input_file) do
{:ok, content} ->
{left_list, right_list} = parse_input(content)
result = calculate_similarity_score(left_list, right_list)
{:ok, result}
{:error, reason} -> {:error, reason}
end
end
defp parse_input(content) do
{left, right} =
content
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, ~r/\s+/, trim: true))
|> Enum.map(fn [left, right] ->
{String.to_integer(left), String.to_integer(right)}
end)
|> Enum.unzip()
{left, right}
end
defp calculate_total_distance(left_list, right_list) do
Enum.zip(Enum.sort(left_list), Enum.sort(right_list))
|> Enum.map(fn {a, b} -> abs(a - b) end)
|> Enum.sum()
end
defp calculate_similarity_score(left_list, right_list) do
frequencies = Enum.frequencies(right_list)
left_list
|> Enum.map(fn num ->
num * Map.get(frequencies, num, 0)
end)
|> Enum.sum()
end
end

View file

@ -1,6 +1,7 @@
defmodule AdventCode2024Test do
use ExUnit.Case
doctest AdventCode2024
alias AdventCode2024.Day1
describe "Day 1: Historian Hysteria" do
test "solves day 1 puzzle with actual input file" do
@ -12,46 +13,9 @@ defmodule AdventCode2024Test do
assert {:error, :enoent} = AdventCode2024.solve_day1("nonexistent.txt")
end
test "parses input string correctly" do
input = "1\t2\n3\t4\n5\t6"
assert {[1, 3, 5], [2, 4, 6]} = AdventCode2024.parse_input(input)
end
test "calculates total distance between two lists using example input" do
left_list = [3, 4, 2, 1, 3, 3]
right_list = [4, 3, 5, 3, 9, 3]
assert AdventCode2024.calculate_total_distance(left_list, right_list) == 11
end
test "handles empty lists" do
assert AdventCode2024.calculate_total_distance([], []) == 0
end
test "handles single element lists" do
assert AdventCode2024.calculate_total_distance([1], [3]) == 2
end
end
describe "Day 1 Part 2: Similarity Score" do
test "calculates similarity score using example input" do
left_list = [3, 4, 2, 1, 3, 3]
right_list = [4, 3, 5, 3, 9, 3]
assert AdventCode2024.calculate_similarity_score(left_list, right_list) == 31
end
test "handles empty lists for similarity score" do
assert AdventCode2024.calculate_similarity_score([], []) == 0
end
test "handles lists with no matches" do
assert AdventCode2024.calculate_similarity_score([1, 2], [3, 4]) == 0
end
test "handles lists with all matches" do
assert AdventCode2024.calculate_similarity_score([1, 1], [1, 1]) == 4
end
test "solves day 1 part 2 puzzle with actual input file" do
assert {:ok, result} = AdventCode2024.solve_day1_part2()