🔧 Setup Advent of Code 2021
This year: Elixir! And maybe python.
This commit is contained in:
commit
d5a29d41e2
18 changed files with 448 additions and 0 deletions
4
2021-elixir/.formatter.exs
Normal file
4
2021-elixir/.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
||||||
26
2021-elixir/.gitignore
vendored
Normal file
26
2021-elixir/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# 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").
|
||||||
|
Aoc-*.tar
|
||||||
|
|
||||||
|
# Ignore ElixirLS VS Code extension
|
||||||
|
.elixir_ls
|
||||||
1
2021-elixir/.tool-versions
Normal file
1
2021-elixir/.tool-versions
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
elixir 1.12.0
|
||||||
29
2021-elixir/README.md
Normal file
29
2021-elixir/README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Aoc
|
||||||
|
|
||||||
|
Solutions for [Advent of Code 2021][1], this year in Elixir.
|
||||||
|
|
||||||
|
[Elixir 1.12.0][2] preferred.
|
||||||
|
|
||||||
|
## Help scripts
|
||||||
|
|
||||||
|
Run tests using `mix test` as usual.
|
||||||
|
|
||||||
|
Create input file, test file and solution file for a day,
|
||||||
|
using day 1 as example:
|
||||||
|
|
||||||
|
mix Aoc.new 1 "Name of the puzzle"
|
||||||
|
|
||||||
|
Solve a single puzzle, using puzzle from day 3 as example:
|
||||||
|
|
||||||
|
mix Aoc.solve 3
|
||||||
|
|
||||||
|
Solve all puzzles, starting at the first:
|
||||||
|
|
||||||
|
mix Aoc.solve_all
|
||||||
|
|
||||||
|
## Log
|
||||||
|
|
||||||
|
_Advent of Code has not started yet. See you in December!_
|
||||||
|
|
||||||
|
[1]: https://adventofcode.com/
|
||||||
|
[2]: https://hexdocs.pm/elixir/Kernel.html
|
||||||
58
2021-elixir/lib/aoc.ex
Normal file
58
2021-elixir/lib/aoc.ex
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
defmodule Aoc do
|
||||||
|
@year 2021
|
||||||
|
def solve_all() do
|
||||||
|
"""
|
||||||
|
|
||||||
|
ADVENT OF CODE #{@year}
|
||||||
|
===================
|
||||||
|
"""
|
||||||
|
|> IO.puts()
|
||||||
|
|
||||||
|
1..25 |> Enum.map(&solve/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def solve(n) do
|
||||||
|
id = n |> Integer.to_string() |> String.pad_leading(2, "0")
|
||||||
|
|
||||||
|
case File.read("./inputs/" <> id <> ".in") do
|
||||||
|
{:error, _} ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
{:ok, input} ->
|
||||||
|
input
|
||||||
|
|> run(id)
|
||||||
|
|> present
|
||||||
|
|> IO.puts()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(input, id) do
|
||||||
|
solution_module = Module.concat(["Aoc.Solution", "Day" <> id])
|
||||||
|
|
||||||
|
data = apply(solution_module, :parse!, [input])
|
||||||
|
name = apply(solution_module, :get_name, [])
|
||||||
|
first_solution = apply(solution_module, :solve_first_part, [data])
|
||||||
|
second_solution = apply(solution_module, :solve_second_part, [data])
|
||||||
|
|
||||||
|
[name, first_solution, second_solution]
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generates a CLI-friendly presentation of the solutions.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Aoc.present(["Day 1: tomten kommer", "10", "20"])
|
||||||
|
"Day 1: tomten kommer\\n--------------------\\n\\n1. 10\\n2. 20\\n"
|
||||||
|
|
||||||
|
"""
|
||||||
|
def present([name, first_solution, second_solution]) do
|
||||||
|
"""
|
||||||
|
#{name}
|
||||||
|
#{1..String.length(name) |> Enum.map_join(fn _x -> "-" end)}
|
||||||
|
|
||||||
|
1. #{first_solution}
|
||||||
|
2. #{second_solution}
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
92
2021-elixir/lib/mix/tasks/bootstrap.ex
Normal file
92
2021-elixir/lib/mix/tasks/bootstrap.ex
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
defmodule Mix.Tasks.Aoc.New do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@shortdoc "Bootstrap new solution"
|
||||||
|
@impl Mix.Task
|
||||||
|
|
||||||
|
@year 2021
|
||||||
|
|
||||||
|
def run([n, name]) do
|
||||||
|
id = n |> String.pad_leading(2, "0")
|
||||||
|
|
||||||
|
input_file = "./inputs/" <> id <> ".in"
|
||||||
|
test_file = "./test/day_" <> id <> "_test.exs"
|
||||||
|
solution_file = "./lib/solutions/day_" <> id <> ".ex"
|
||||||
|
|
||||||
|
IO.puts("Creating " <> input_file)
|
||||||
|
File.touch(input_file)
|
||||||
|
|
||||||
|
IO.puts("Creating " <> test_file)
|
||||||
|
File.write!(test_file, test_file_content(id))
|
||||||
|
|
||||||
|
IO.puts("Creating " <> solution_file)
|
||||||
|
File.write!(solution_file, solution_file_content(name, id, n))
|
||||||
|
|
||||||
|
"""
|
||||||
|
\nDone! Start coding.
|
||||||
|
|
||||||
|
Get your puzzle input here:
|
||||||
|
https://adventofcode.com/#{@year}/day/#{n}/input
|
||||||
|
|
||||||
|
mix test -- run tests.
|
||||||
|
mix aoc.solve_all -- run all puzzles, starting with 1
|
||||||
|
mix aoc.solve #{id} -- run single puzzle, 1-25
|
||||||
|
"""
|
||||||
|
|> IO.puts()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp test_file_content(id) do
|
||||||
|
"""
|
||||||
|
defmodule Day#{id}Test do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest Aoc.Solution.Day#{id}
|
||||||
|
import Aoc.Solution.Day#{id}
|
||||||
|
|
||||||
|
test "parses the input" do
|
||||||
|
expected = 10
|
||||||
|
|
||||||
|
assert parse!("10") == expected
|
||||||
|
end
|
||||||
|
|
||||||
|
test "solves first part" do
|
||||||
|
a = "something" |> parse!() |> solve_first_part()
|
||||||
|
|
||||||
|
assert a == :something
|
||||||
|
end
|
||||||
|
|
||||||
|
test "solves second part" do
|
||||||
|
a = "something" |> parse!() |> solve_second_part()
|
||||||
|
|
||||||
|
assert a == :something
|
||||||
|
end
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp solution_file_content(name, id, n) do
|
||||||
|
~s"""
|
||||||
|
defmodule Aoc.Solution.Day#{id} do
|
||||||
|
@name "Day #{n}: #{name}"
|
||||||
|
@behaviour Solution
|
||||||
|
|
||||||
|
@impl Solution
|
||||||
|
def get_name, do: @name
|
||||||
|
|
||||||
|
@impl Solution
|
||||||
|
def parse!(_raw) do
|
||||||
|
"10"
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Solution
|
||||||
|
def solve_first_part(_input) do
|
||||||
|
"(TBW)"
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Solution
|
||||||
|
def solve_second_part(_input) do
|
||||||
|
"(TBW)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
11
2021-elixir/lib/mix/tasks/solve.ex
Normal file
11
2021-elixir/lib/mix/tasks/solve.ex
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Mix.Tasks.Aoc.Solve do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@shortdoc "Solve single puzzle"
|
||||||
|
@impl Mix.Task
|
||||||
|
def run([id]) do
|
||||||
|
id
|
||||||
|
|> String.to_integer()
|
||||||
|
|> Aoc.solve()
|
||||||
|
end
|
||||||
|
end
|
||||||
9
2021-elixir/lib/mix/tasks/solve_all.ex
Normal file
9
2021-elixir/lib/mix/tasks/solve_all.ex
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Mix.Tasks.Aoc.SolveAll do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@shortdoc "Solve all puzzles"
|
||||||
|
@impl Mix.Task
|
||||||
|
def run(_) do
|
||||||
|
Aoc.solve_all()
|
||||||
|
end
|
||||||
|
end
|
||||||
6
2021-elixir/lib/solution.ex
Normal file
6
2021-elixir/lib/solution.ex
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
defmodule Solution do
|
||||||
|
@callback parse!(String.t()) :: Any
|
||||||
|
@callback get_name() :: String.t()
|
||||||
|
@callback solve_first_part(Any) :: String.t()
|
||||||
|
@callback solve_second_part(Any) :: String.t()
|
||||||
|
end
|
||||||
28
2021-elixir/mix.exs
Normal file
28
2021-elixir/mix.exs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Aoc.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :Aoc,
|
||||||
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.10",
|
||||||
|
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
|
||||||
4
2021-elixir/test/aoc_test.exs
Normal file
4
2021-elixir/test/aoc_test.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule AocTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest Aoc
|
||||||
|
end
|
||||||
1
2021-elixir/test/test_helper.exs
Normal file
1
2021-elixir/test/test_helper.exs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ExUnit.start()
|
||||||
4
2021-python/.gitignore
vendored
Normal file
4
2021-python/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
.idea
|
||||||
|
*.pyc
|
||||||
|
__pycache__
|
||||||
|
.vscode
|
||||||
1
2021-python/.python-version
Normal file
1
2021-python/.python-version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3.8.0
|
||||||
29
2021-python/README.md
Normal file
29
2021-python/README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
Advent of Code 2021
|
||||||
|
===================
|
||||||
|
|
||||||
|
Solutions for #aoc2020 in Python 3 (3.8+).
|
||||||
|
|
||||||
|
Help scripts
|
||||||
|
------------
|
||||||
|
|
||||||
|
Solve all puzzles:
|
||||||
|
|
||||||
|
python aoc.py
|
||||||
|
|
||||||
|
To bootstrap a new puzzle (creates `inputs/<day_no>.txt`, `solutions/day_<day_no>.py` och
|
||||||
|
`tests/test_day_<day_no>.py`):
|
||||||
|
|
||||||
|
python aoc.py <dag_no> "<puzzle_name>"
|
||||||
|
|
||||||
|
Manually copy the puzzle input from https://adventofcode.com and paste it in `inputs/<day_no>.txt`
|
||||||
|
to start coding.
|
||||||
|
|
||||||
|
Solve separate puzzle (replace `XX` with the puzzle number):
|
||||||
|
|
||||||
|
python -m solutions.day_XX
|
||||||
|
|
||||||
|
Run tests (replace `XX` with the puzzle number):
|
||||||
|
|
||||||
|
python -m unittest --locals -v
|
||||||
|
# or, if `pytest` is preferred:
|
||||||
|
pytest
|
||||||
113
2021-python/aoc.py
Normal file
113
2021-python/aoc.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
year = 2021
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, day_no, name = sys.argv
|
||||||
|
except ValueError:
|
||||||
|
day_no = None
|
||||||
|
name = None
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"\nAdvent of Code {year}"
|
||||||
|
"\n###################"
|
||||||
|
"\n\nby Anders Ytterström (@madr_se)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if day_no and name:
|
||||||
|
print(f"\n- creating solutions/day_{day_no.zfill(2)}.py")
|
||||||
|
with open("solutions/day_{}.py".format(day_no.zfill(2)), "w") as s:
|
||||||
|
s.write(
|
||||||
|
"""
|
||||||
|
from solutions import BaseSolution
|
||||||
|
|
||||||
|
|
||||||
|
class Solution(BaseSolution):
|
||||||
|
input_file = "{day_no}.txt"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Day {day}: {name}"
|
||||||
|
|
||||||
|
def parse_input(self, data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
def solve(self, puzzle_input):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def solve_again(self, puzzle_input):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
solution = Solution()
|
||||||
|
solution.show_results()
|
||||||
|
""".strip().format(
|
||||||
|
day=day_no, day_no=day_no.zfill(2), name=name
|
||||||
|
)
|
||||||
|
+ "\n"
|
||||||
|
)
|
||||||
|
print(f"- creating tests/test_day_{day_no.zfill(2)}.py")
|
||||||
|
with open("tests/test_day_{}.py".format(day_no.zfill(2)), "w") as t:
|
||||||
|
t.write(
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from solutions.day_{day_no} import Solution
|
||||||
|
|
||||||
|
|
||||||
|
class Day{day_no}TestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.solution = Solution()
|
||||||
|
self.puzzle_input = self.solution.parse_input(
|
||||||
|
\"\"\"
|
||||||
|
<REPLACE ME>
|
||||||
|
\"\"\"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parse_puzzle_input(self):
|
||||||
|
data = \"\"\"
|
||||||
|
<REPLACE ME>
|
||||||
|
\"\"\"
|
||||||
|
assert self.solution.parse_input(data) == "<REPLACE ME>"
|
||||||
|
|
||||||
|
# def test_solve_first_part(self):
|
||||||
|
# assert self.solution.solve(self.puzzle_input) == True
|
||||||
|
|
||||||
|
# def test_solve_second_part(self):
|
||||||
|
# assert self.solution.solve_again(self.puzzle_input) == True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
""".strip().format(
|
||||||
|
day_no=day_no.zfill(2)
|
||||||
|
)
|
||||||
|
+ "\n"
|
||||||
|
)
|
||||||
|
print(f"- creating empty inputs/{day_no.zfill(2)}.txt")
|
||||||
|
with open("inputs/{}.txt".format(day_no.zfill(2)), "w") as i:
|
||||||
|
i.write("")
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"""
|
||||||
|
Done! start coding.
|
||||||
|
|
||||||
|
Puzzle link:
|
||||||
|
https://adventofcode.com/{year}/day/{day_no}
|
||||||
|
|
||||||
|
Puzzle input (copy and paste to inputs/{day_no.zfill(2)}.txt):
|
||||||
|
https://adventofcode.com/{year}/day/{day_no}/input
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
for i in [str(n).zfill(2) for n in range(1, 26)]:
|
||||||
|
try:
|
||||||
|
solution = __import__(
|
||||||
|
"solutions.day_{}".format(i), globals(), locals(), ["Solution"], 0
|
||||||
|
).Solution()
|
||||||
|
solution.show_results()
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
32
2021-python/solutions/__init__.py
Normal file
32
2021-python/solutions/__init__.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
class BaseSolution:
|
||||||
|
input_file = None
|
||||||
|
trim_input = True
|
||||||
|
|
||||||
|
def read_input(self, filename):
|
||||||
|
filepath = "./inputs/{}".format(filename)
|
||||||
|
with open(filepath, "r") as f:
|
||||||
|
data = f.read()
|
||||||
|
if self.trim_input:
|
||||||
|
return data.strip()
|
||||||
|
return data
|
||||||
|
|
||||||
|
def show_results(self):
|
||||||
|
data = self.read_input(self.input_file)
|
||||||
|
puzzle_input = self.parse_input(data)
|
||||||
|
print(
|
||||||
|
"\n\n{}\n{}\n\nPart 1: {}\nPart 2: {}".format(
|
||||||
|
str(self),
|
||||||
|
"-" * len(str(self)),
|
||||||
|
self.solve(puzzle_input),
|
||||||
|
self.solve_again(puzzle_input),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def solve(self, puzzle_input):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def solve_again(self, puzzle_input):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def parse_input(self, data):
|
||||||
|
raise NotImplemented
|
||||||
0
2021-python/tests/__init__.py
Normal file
0
2021-python/tests/__init__.py
Normal file
Loading…
Add table
Reference in a new issue