Initial release
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
+36
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Executable
+72
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
defmodule Aoc2024Test do
|
||||
use ExUnit.Case
|
||||
doctest Aoc2024
|
||||
|
||||
test "greets the world" do
|
||||
assert Aoc2024.hello() == :world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
ExUnit.start()
|
||||
Reference in New Issue
Block a user