defmodule Day01 do
def part1(input) do
all = parse(input)
{first, second} = Enum.unzip(all)
Enum.zip([Enum.sort(first), Enum.sort(second)])
|> Enum.map(fn {a, b} ->
abs(a - b)
end)
|> Enum.sum
end
def part2(input) do
all = parse(input)
{first, second} = Enum.unzip(all)
frequencies = Enum.frequencies(second)
first
|> Enum.map(fn n ->
n * Map.get(frequencies, n, 0)
end)
|> Enum.sum
end
defp parse(input) do
input
|> Enum.map(fn line ->
{first, line} = Integer.parse(line)
line = String.trim(line)
{second, ""} = Integer.parse(line)
{first, second}
end)
end
end
def run(puzzle) do
{l1, l2} = Part1.parse(puzzle)
l2_frequencies = Enum.frequencies(l2)
Enum.reduce(l1, 0, fn n, acc ->
acc + n * Map.get(l2_frequencies, n, 0)
end)
end
defmodule AdventOfCode.Solutions.Y24.Day01 do
alias AoC.Input
def parse(input, _part) do
input
|> Input.stream!(trim: true)
|> Enum.map(&parse_line/1)
|> Enum.unzip()
end
defp parse_line(line) do
[a, b] = String.split(line, " ", trim: true)
{a, ""} = Integer.parse(a)
{b, ""} = Integer.parse(b)
{a, b}
end
def part_one(problem) do
{left, right} = problem
left = Enum.sort(left)
right = Enum.sort(right)
Enum.zip_with(left, right, fn a, b -> abs(a - b) end) |> Enum.sum()
end
def part_two(problem) do
{left, right} = problem
freqs = Enum.frequencies(right)
left |> Enum.map(fn a -> a * Map.get(freqs, a, 0) end) |> Enum.sum()
end
end
Are we not supposed to also include code for opening and reading the input from a file? Or we can opt to just put it in a module attribute and parse that?
I made my own utility modules and functions because I’ve been using Elixir for the past 3-4 AoCs so I’ve accumulated use cases. The goal of posts should be the actual solution, it’s less important where the input comes from!
First time trying to do anything in elixir. I’m still learning in early stage and sometimes it twist my mind to be able to express what i want to do. I think i overcomplicated it a lot, but at least got the correct result
defmodule Aoc2024.Day1 do
def getresult do
{:ok, contents} = File.read("input")
# [ %{left: 40094, right: 37480}, %{left: 52117, right: 14510}, ... ]
itemlist =
contents
|> String.split("\n")
|> Enum.map(fn(line) -> splitlist(line) end)
# Sorted list with only left values
left_items =
itemlist
|> Enum.sort(fn(i1, i2) -> order_items(i1, i2, :left) end)
|> Enum.map(fn(i) -> i.left end)
# same for the right values
right_items =
itemlist
|> Enum.sort(fn(i1, i2) -> order_items(i1, i2, :right) end)
|> Enum.map(fn(i) -> i.right end)
# List of diff of each left and right value at the same list position
difflist =
Enum.zip(left_items, right_items)
|> Enum.map(fn{li, ri} -> abs(li - ri) end)
# sum the values via recursive add
add_values(difflist, 0)
end
defp splitlist(line) do
[left , right] = line |> String.split(" ", trim: true)
%{left: String.to_integer(left) , right: String.to_integer(right)}
end
defp order_items(i1, i2, side) do
case side do
:left -> i1.left <= i2.left
:right -> i1.right <= i2.right
end
end
defp add_values([h | t], result) do
h + add_values(t, result)
end
defp add_values([], result) do
result
end
end
IO.puts(Integer.to_string(Aoc2024.Day1.getresult))
Here’s mine - I did it in Livebook which was a real help, coming from a python/F#-ish world:
# ── Setup ──
fname = "/Users/marksmith/Downloads/AOC24/Day01/input.txt"
lines = File.stream!(fname)
|> Enum.map(&String.split/1)
# ── Part 1 ──
{left, right} = lines
|> Enum.map(&List.to_tuple/1)
|> Enum.map( fn {first,second} -> {String.to_integer(first), String.to_integer(second)} end)
|> Enum.unzip()
left = Enum.sort(left)
right = Enum.sort(right)
first_answer = Enum.zip(left, right)
|> Enum.map(fn {l,r} -> abs(l-r) end)
|> Enum.sum()
# ── Part 2 ──
right_freqs = Enum.frequencies(right)
defmodule Etc do
def match_val(x, rf) do
{_, freq} = Enum.find(rf, {x, 0}, fn {a,_} -> a == x end)
freq
end
end
second_answer = left
|> Enum.map(fn x -> {x, Etc.match_val(x, right_freqs)} end)
|> Enum.map(fn {x,y} -> x * y end)
|> Enum.sum()
There’s probably a more compact way of doing the unzip/sort/rezip bit in Part1 but I wanted to be able to understand it when I looked back on it .
For Part 2 I struggled to extract the second element of a tuple having just found it, so it ended up in a function that returned the second element. I wanted to write that as an anonymous function but struggled with the syntax so went with the defmodule/def approach.
Here’s mine. Much more coding lines than I’d like but I guess my work bleeds into everything. I like small functions and separation of concerns and doctests and typespecs.
defmodule Day01 do
@type id :: non_neg_integer()
@type pair :: {id(), id()}
@type pairs :: [pair()]
@type distance :: non_neg_integer()
@type similarity :: non_neg_integer()
@spec parse_line(String.t()) :: pair()
def parse_line(line) do
line
|> String.trim()
|> String.split(~r/\s+/)
|> Enum.map(fn text ->
{number, ""} = Integer.parse(text)
number
end)
|> List.to_tuple()
end
@spec parse_input(String.t()) :: [pair()]
def parse_input(input) do
input
|> String.split("\n")
|> Enum.reject(fn line -> line == "" end)
|> Enum.map(&parse_line/1)
end
@spec sum_of_distances(pairs()) :: distance()
def sum_of_distances(input) do
{left, right} = Enum.unzip(input)
left = Enum.sort(left)
right = Enum.sort(right)
left
|> Enum.zip(right)
|> Enum.map(fn {a, b} -> abs(a - b) end)
|> Enum.sum()
end
# @spec similarity_score(pairs()) :: similarity()
def similarity_score(input) do
{left, right} = Enum.unzip(input)
frequencies = Enum.frequencies(right)
left
|> Enum.map(fn n -> n * Map.get(frequencies, n, 0) end)
|> Enum.sum()
end
@doc ~S"""
iex> Day01.part_1("3 4\n4 3\n2 5\n1 3\n3 9\n3 3\n")
11
"""
@spec part_1(String.t()) :: distance()
def part_1(input \\ input()) do
input |> parse_input() |> sum_of_distances()
end
@doc ~S"""
iex> Day01.part_2("3 4\n4 3\n2 5\n1 3\n3 9\n3 3\n")
31
iex> Day01.part_2("3 4\n4 3\n2 3\n1 3\n3 9\n3 3\n")
40
"""
@spec part_2(String.t()) :: similarity()
def part_2(input \\ input()) do
input |> parse_input() |> similarity_score()
end
def input(), do: File.read!("input/day_01.txt")
end
Since I did not want to cheat I only checked the solutions of others post factum and interestingly enough mine is almost identical to @bjorng.