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.