Initial release

This commit is contained in:
Aurélien Geron
2024-12-06 19:44:29 +13:00
commit adfaafec83
10 changed files with 1323 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
+36
View File
@@ -0,0 +1,36 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
aoc2024-*.tar
# Temporary files, for example, from tests.
/tmp/
# MacOS
.DS_Store
# VSCode
*.code-workspace
# AoC Session File
.session
+44
View File
@@ -0,0 +1,44 @@
# Advent of Code 2024 in Elixir
If you've never heard of Advent of Code, you're missing out. Check it out now at [adventofcode.com](https://adventofcode.com/about)!
I mostly do AoC puzzles for fun, generally coding in Python or Rust, but I've also found that it's a great way to get some experience with other programming languages, such as Elixir, Haskell, or Julia.
This repository contains all my solutions to the [AoC 2024](https://adventofcode.com/2024) puzzles using Elixir.
As I'm still learning Elixir, the code may not always be as simple or idiomatic as it could be. If you have suggestions for things I could improve, please don't hesitate to file an issue or submit a PR. Thanks!
## Usage
Make sure you have Elixir and Git installed, then open a terminal and run:
```shell
git clone https://github.com/ageron/aoc2024-elixir
cd aoc2024-elixir
mix aoc2024.run
```
You can specify days to run if you want, for example this will run days 2, 4, and 6:
```
mix aoc2024.run 2 4 6
```
## Getting the data
I've also included a little `get_data.py` utility to automatically download the data of the day, at the right time (you'll get a countdown if you're early). I was too lazy to code it in Elixir, but perhaps I'll port it one day. To use it, just type the following command in a terminal, replacing `{day}` with the day you want:
```
cd /path/to/this/repository
python get_data.py 2024 {day}
```
The script requires the `requests` and `pytz` libraries, which you can install like this:
```
python -m pip install --user requests pytz
```
The first time you run `get_data.py`, you will be asked to login to AoC in your browser, [find your session cookie](https://github.com/wimglenn/advent-of-code-wim/issues/1), and save it into a `.session` file in the current directory.
Have fun!
+1000
View File
File diff suppressed because it is too large Load Diff
Executable
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env python
import sys
import requests
from datetime import datetime
from pytz import timezone
from time import sleep
from pathlib import Path
# Eric Wastl, the author of Advent-of-Code, asked that if you're automatically
# querying adventofcode.com (as this script does), then you should include
# contact details in the User-Agent field. So please replace my name with yours:
NAME = "Aurélien Geron"
def usage():
print("Usage:")
print(f"{sys.argv[0]} {{year}} {{day}}")
sys.exit(1)
def session_error():
print("""Please open your browser, login to adventofcode.com, lookup
the session cookie, and save its value to the .session file.
Here's how to find this cookie in Chrome: right-click > Inspect, select the
Application tab in the inspector, then in the left menu select
Storage > Cookies > https://adventofcode.com, and click on session in the list.
Copy the cookie value: it's a long hexadecimal .""")
sys.exit(2)
if len(sys.argv) != 3:
usage()
try:
year, day = map(int, sys.argv[1:3])
except ValueError:
usage()
session_path = Path(".session")
if not session_path.is_file():
session_error()
eastern = timezone('US/Eastern')
requested_day = eastern.localize(datetime(year, 12, day))
while True:
now = datetime.now(eastern)
seconds_left = (requested_day - now).total_seconds()
if seconds_left > 0:
wait_time = seconds_left % 1
if wait_time < 0.5:
wait_time += 1
sleep(wait_time)
print(f"{int(seconds_left)} ", end="\r");
else:
break
sleep(1) # just to be safe
url = f"https://adventofcode.com/{year}/day/{day}/input"
cookies = {"session": open(".session").read().strip()}
headers = {"User-Agent": NAME}
request = requests.get(url, cookies=cookies, headers=headers)
if request.status_code != 200:
print(f"HTTP Error {request.status_code}")
print(request.text)
session_error()
text = request.text
if year == 2023:
with open(f"data/day{day:02}.txt", "w") as f:
f.write(text)
else:
print(text)
+41
View File
@@ -0,0 +1,41 @@
defmodule Aoc2024.Day01 do
def parse(input) do
numbers =
input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
line
|> String.split(~r/\s+/)
|> Enum.map(fn int_str ->
case int_str |> Integer.parse() do
{int, ""} -> int
_ -> raise "Invalid integer #{int_str}"
end
end)
end)
numbers
|> Enum.map(&List.to_tuple/1)
|> Enum.unzip()
end
def part1({col0, col1}) do
sorted_col0 = col0 |> Enum.sort()
sorted_col1 = col1 |> Enum.sort()
Enum.zip(sorted_col0, sorted_col1)
|> Enum.map(fn {val0, val1} ->
abs(val0 - val1)
end)
|> Enum.sum()
end
def part2({col0, col1}) do
col0
|> Enum.map(fn val0 ->
count = Enum.count(col1, fn val1 -> val0 == val1 end)
count * val0
end)
|> Enum.sum()
end
end
+89
View File
@@ -0,0 +1,89 @@
defmodule Mix.Tasks.Aoc2024.Run do
use Mix.Task
@shortdoc "Run all the solutions, or just the ones for the given days"
def run(args) do
days =
if args |> Enum.empty?() do
1..25 |> Enum.map(&Integer.to_string/1)
else
args
end
days |> Enum.each(&run_day/1)
end
defp check_and_pad_day(day_str) do
case day_str |> Integer.parse() do
{day, ""} when day >= 1 and day <= 25 ->
IO.puts("---- Day #{day}")
{:ok, day |> Integer.to_string() |> String.pad_leading(2, "0")}
_ ->
{:error, "Invalid day `#{day_str}`"}
end
end
defp load_module(padded_day_str) do
module = Module.concat([Aoc2024, "Day#{padded_day_str}"])
if match?({:module, _}, Code.ensure_loaded(module)) do
{:ok, module}
else
{:error, "Module `#{inspect(module)}` is not defined."}
end
end
defp load_input(padded_day_str) do
file_path = "data/day_#{padded_day_str}.txt"
case File.read(file_path) do
{:ok, content} ->
{:ok, content}
{:error, reason} ->
{:error, "Failed to read #{file_path}: #{reason}"}
end
end
defp parse_input(module, input) do
if function_exported?(module, :parse, 1) do
{:ok, module.parse(input)}
else
{:ok, input}
end
end
defp run_part1(module, parsed) do
if function_exported?(module, :part1, 1) do
result1 = module.part1(parsed)
IO.inspect(result1)
{:ok, true}
else
{:error, "Function `#{inspect(module)}.part1/1` is not defined."}
end
end
defp run_part2(module, parsed) do
if function_exported?(module, :part2, 1) do
result2 = module.part2(parsed)
IO.inspect(result2)
{:ok, true}
else
{:error, "Function `#{inspect(module)}.part2/1` is not defined."}
end
end
defp run_day(day_str) do
with {:ok, padded_day_str} <- check_and_pad_day(day_str),
{:ok, module} <- load_module(padded_day_str),
{:ok, input} <- load_input(padded_day_str),
{:ok, parsed_input} <- parse_input(module, input),
{:ok, _success} <- run_part1(module, parsed_input),
{:ok, _success} <- run_part2(module, parsed_input) do
else
{:error, msg} ->
IO.puts("Error: #{msg}")
end
end
end
+28
View File
@@ -0,0 +1,28 @@
defmodule Aoc2024.MixProject do
use Mix.Project
def project do
[
app: :aoc2024,
version: "0.1.0",
elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end
+8
View File
@@ -0,0 +1,8 @@
defmodule Aoc2024Test do
use ExUnit.Case
doctest Aoc2024
test "greets the world" do
assert Aoc2024.hello() == :world
end
end
+1
View File
@@ -0,0 +1 @@
ExUnit.start()