Setup 2021 (#1)
* chore: scaffold project * chore: stand up puzzle solver, puzzle case * feat: solution runner * feat: solution generator
This commit is contained in:
parent
da99ce538d
commit
a691461882
18 changed files with 357 additions and 0 deletions
5
2021/.formatter.exs
Normal file
5
2021/.formatter.exs
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
|
||||
locals_without_parens: [assert_solution: 2]
|
||||
]
|
27
2021/.gitignore
vendored
Normal file
27
2021/.gitignore
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
# 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").
|
||||
advent_of_code-*.tar
|
||||
|
||||
|
||||
# Temporary files for e.g. tests
|
||||
/tmp
|
2
2021/.tool-versions
Normal file
2
2021/.tool-versions
Normal file
|
@ -0,0 +1,2 @@
|
|||
erlang 24.1.4
|
||||
elixir 1.12.3-otp-24
|
22
2021/README.md
Normal file
22
2021/README.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Advent of Code 2021
|
||||
|
||||
<details>
|
||||
<summary>Setup</summary>
|
||||
|
||||
Using [asdf]:
|
||||
|
||||
```sh
|
||||
asdf plugin add erlang
|
||||
asdf plugin add elixir
|
||||
asdf install
|
||||
```
|
||||
</details>
|
||||
|
||||
| S | M | T | W | T | F | S |
|
||||
| :-: | :-: | :-: | :-: | :-: | :-: | :-: |
|
||||
| | | | 1 | 2 | 3 | 4 |
|
||||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|
||||
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
|
||||
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
|
||||
|
||||
[asdf]: https://asdf-vm.com/#/
|
12
2021/lib/advent_of_code.ex
Normal file
12
2021/lib/advent_of_code.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule AdventOfCode do
|
||||
@moduledoc """
|
||||
Solutions to the 2021 Advent of Code puzzles
|
||||
"""
|
||||
|
||||
def solver(selector) do
|
||||
[day, part] = String.split(selector, ".")
|
||||
day_module = Macro.camelize("Day#{day}")
|
||||
part_module = Macro.camelize("Part#{part}")
|
||||
Module.concat([AdventOfCode, day_module, part_module])
|
||||
end
|
||||
end
|
0
2021/lib/advent_of_code/day0.ex
Normal file
0
2021/lib/advent_of_code/day0.ex
Normal file
11
2021/lib/advent_of_code/day0/part0.ex
Normal file
11
2021/lib/advent_of_code/day0/part0.ex
Normal file
|
@ -0,0 +1,11 @@
|
|||
defmodule AdventOfCode.Day0.Part0 do
|
||||
alias AdventOfCode.PuzzleSolver
|
||||
|
||||
use PuzzleSolver
|
||||
|
||||
@impl PuzzleSolver
|
||||
def solve(stream) do
|
||||
Stream.run(stream)
|
||||
"42"
|
||||
end
|
||||
end
|
18
2021/lib/advent_of_code/puzzle_solver.ex
Normal file
18
2021/lib/advent_of_code/puzzle_solver.ex
Normal file
|
@ -0,0 +1,18 @@
|
|||
defmodule AdventOfCode.PuzzleSolver do
|
||||
@moduledoc """
|
||||
Behaviour for a puzzle solution.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Given the input as a stream, return the solution as a string
|
||||
"""
|
||||
@callback solve(Enumerable.t()) :: String.t()
|
||||
|
||||
def solve(mod, stream), do: apply(mod, :solve, [stream])
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour AdventOfCode.PuzzleSolver
|
||||
end
|
||||
end
|
||||
end
|
13
2021/lib/mix/advent_of_code.ex
Normal file
13
2021/lib/mix/advent_of_code.ex
Normal file
|
@ -0,0 +1,13 @@
|
|||
defmodule Mix.AdventOfCode do
|
||||
@moduledoc """
|
||||
Helpers for `AdventOfCode` mix tasks.
|
||||
"""
|
||||
|
||||
def day_module(day) do
|
||||
Module.concat(AdventOfCode, Macro.camelize("Day#{day}"))
|
||||
end
|
||||
|
||||
def part_module(day, part) do
|
||||
Module.concat(day_module(day), Macro.camelize("Part#{part}"))
|
||||
end
|
||||
end
|
94
2021/lib/mix/tasks/advent_of_code.gen.solution.ex
Normal file
94
2021/lib/mix/tasks/advent_of_code.gen.solution.ex
Normal file
|
@ -0,0 +1,94 @@
|
|||
defmodule Mix.Tasks.AdventOfCode.Gen.Solution do
|
||||
use Mix.Task
|
||||
import Mix.Generator
|
||||
import Mix.AdventOfCode
|
||||
|
||||
@shortdoc "Generate a new solution module"
|
||||
|
||||
@moduledoc """
|
||||
#{@shortdoc}.
|
||||
|
||||
Includes new solution module, test, and empty problem input.
|
||||
|
||||
## Examples
|
||||
|
||||
# Generate solution modules, tests, and empty input for Day 2
|
||||
$ mix advent_of_code.gen.solution 2
|
||||
"""
|
||||
|
||||
@switches []
|
||||
|
||||
@impl Mix.Task
|
||||
def run(args) do
|
||||
case OptionParser.parse!(args, switches: @switches) do
|
||||
{_, [day]} ->
|
||||
day_module = day_module(day)
|
||||
day_contents = day_template(mod: day_module) |> Code.format_string!()
|
||||
day_path = Path.join("lib", Macro.underscore(day_module))
|
||||
day_tests_path = Path.join("test", Macro.underscore(day_module))
|
||||
day_file = "#{day_path}.ex"
|
||||
|
||||
create_directory(day_path)
|
||||
create_directory(day_tests_path)
|
||||
create_file(day_file, day_contents)
|
||||
|
||||
for part <- 1..2 do
|
||||
part_module = part_module(day, part)
|
||||
|
||||
part_contents =
|
||||
part_template(mod: part_module, day_mod: day_module) |> Code.format_string!()
|
||||
|
||||
part_file = Path.join("lib", "#{Macro.underscore(part_module)}.ex")
|
||||
create_file(part_file, part_contents)
|
||||
|
||||
part_test_module = Module.concat(day_module, Macro.camelize("Part#{part}Test"))
|
||||
|
||||
part_test_contents =
|
||||
part_test_template(mod: part_module, test_mod: part_test_module)
|
||||
|> Code.format_string!()
|
||||
|
||||
part_test_file =
|
||||
Path.join(day_tests_path, "#{Macro.underscore("part_#{part}_test")}.exs")
|
||||
|
||||
create_file(part_test_file, part_test_contents)
|
||||
end
|
||||
|
||||
_ ->
|
||||
Mix.raise("Unknown arguments.")
|
||||
end
|
||||
end
|
||||
|
||||
embed_template(:day, ~S"""
|
||||
defmodule <%= inspect(@mod) %> do
|
||||
|
||||
end
|
||||
""")
|
||||
|
||||
embed_template(:part, ~S"""
|
||||
defmodule <%= inspect(@mod) %> do
|
||||
alias AdventOfCode.PuzzleSolver
|
||||
use PuzzleSolver
|
||||
|
||||
import <%= inspect(@day_mod) %>, warn: false
|
||||
|
||||
@impl PuzzleSolver
|
||||
def solve(_input_stream) do
|
||||
:ok |> to_string()
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
embed_template(:part_test, ~S[
|
||||
defmodule <%= inspect(@test_mod) %> do
|
||||
use AdventOfCode.PuzzleCase, module: <%= inspect(@mod) %>
|
||||
|
||||
test "returns :ok" do
|
||||
input = ~S"""
|
||||
input
|
||||
"""
|
||||
|
||||
assert_solution input, "ok"
|
||||
end
|
||||
end
|
||||
])
|
||||
end
|
73
2021/lib/mix/tasks/advent_of_code.solve.ex
Normal file
73
2021/lib/mix/tasks/advent_of_code.solve.ex
Normal file
|
@ -0,0 +1,73 @@
|
|||
defmodule Mix.Tasks.AdventOfCode.Solve do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Runs solution code with problem input"
|
||||
|
||||
@moduledoc """
|
||||
#{@shortdoc}.
|
||||
|
||||
## Options
|
||||
|
||||
`--input`
|
||||
- name of a `.input` file in `priv/inputs/` without the `.input` extension or `-` to read from stdin
|
||||
|
||||
## Examples
|
||||
|
||||
# Run Day 2, Part 1 solution program against the `2.1.input` file
|
||||
$ mix advent_of_code.solve 2.1
|
||||
|
||||
# Run Day 1, Part 2 with a special input
|
||||
$ mix advent_of_code.solve 1.2 --input day-1-part-2-small # priv/inputs/day-1-part-2-small.input
|
||||
|
||||
# Run Day 17, Part 1 with input from stdin
|
||||
# $ mix advent_of_code.solve 17.1 --input -
|
||||
"""
|
||||
|
||||
@switches [input: :string]
|
||||
|
||||
@impl Mix.Task
|
||||
def run(args) do
|
||||
case OptionParser.parse!(args, strict: @switches) do
|
||||
{opts, [selector]} ->
|
||||
case stream_for_input(selector, opts) do
|
||||
{:indeterminate, stream} ->
|
||||
ProgressBar.render_spinner(
|
||||
[text: "Processing indeterminate data...", done: "Done."],
|
||||
fn -> solve(selector, stream) end
|
||||
)
|
||||
|> IO.puts()
|
||||
|
||||
{size, stream} ->
|
||||
stream =
|
||||
stream
|
||||
|> Stream.with_index(1)
|
||||
|> Stream.each(fn {_, i} -> ProgressBar.render(i, size) end)
|
||||
|> Stream.map(fn {l, _} -> l end)
|
||||
|
||||
solve(selector, stream)
|
||||
|> IO.puts()
|
||||
end
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp solve(selector, stream) do
|
||||
selector
|
||||
|> AdventOfCode.solver()
|
||||
|> AdventOfCode.PuzzleSolver.solve(stream)
|
||||
end
|
||||
|
||||
defp stream_for_input(selector, opts) do
|
||||
input = Keyword.get(opts, :input, selector)
|
||||
|
||||
if input == "-" do
|
||||
{:indeterminate, IO.stream()}
|
||||
else
|
||||
file = Path.join("priv/inputs", "#{input}.input")
|
||||
stream = File.stream!(file)
|
||||
{Enum.count(stream), stream}
|
||||
end
|
||||
end
|
||||
end
|
31
2021/mix.exs
Normal file
31
2021/mix.exs
Normal file
|
@ -0,0 +1,31 @@
|
|||
defmodule AdventOfCode.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :advent_of_code,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.11",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger, :eex]
|
||||
]
|
||||
end
|
||||
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:progress_bar, "~> 2.0"}
|
||||
]
|
||||
end
|
||||
end
|
4
2021/mix.lock
Normal file
4
2021/mix.lock
Normal file
|
@ -0,0 +1,4 @@
|
|||
%{
|
||||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
||||
"progress_bar": {:hex, :progress_bar, "2.0.1", "7b40200112ae533d5adceb80ff75fbe66dc753bca5f6c55c073bfc122d71896d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "2519eb58a2f149a3a094e729378256d8cb6d96a259ec94841bd69fdc71f18f87"},
|
||||
}
|
3
2021/priv/inputs/0.0.input
Normal file
3
2021/priv/inputs/0.0.input
Normal file
|
@ -0,0 +1,3 @@
|
|||
foo
|
||||
bar
|
||||
baz
|
7
2021/test/advent_of_code/day0/part_0_test.exs
Normal file
7
2021/test/advent_of_code/day0/part_0_test.exs
Normal file
|
@ -0,0 +1,7 @@
|
|||
defmodule AdventOfCode.Day0.Part0Test do
|
||||
use AdventOfCode.PuzzleCase, module: AdventOfCode.Day0.Part0
|
||||
|
||||
test "returns the answer to live the universe and everything" do
|
||||
assert_solution "life the universe and everything", "42"
|
||||
end
|
||||
end
|
13
2021/test/advent_of_code_test.exs
Normal file
13
2021/test/advent_of_code_test.exs
Normal file
|
@ -0,0 +1,13 @@
|
|||
defmodule AdventOfCodeTest do
|
||||
use ExUnit.Case
|
||||
|
||||
import AdventOfCode
|
||||
|
||||
describe "solver/1" do
|
||||
test "returns a module for a solver" do
|
||||
assert solver("1.1") == AdventOfCode.Day1.Part1
|
||||
assert solver("2.2") == AdventOfCode.Day2.Part2
|
||||
assert solver("3.1") == AdventOfCode.Day3.Part1
|
||||
end
|
||||
end
|
||||
end
|
21
2021/test/support/puzzle_case.ex
Normal file
21
2021/test/support/puzzle_case.ex
Normal file
|
@ -0,0 +1,21 @@
|
|||
defmodule AdventOfCode.PuzzleCase do
|
||||
@moduledoc """
|
||||
Defines tests for an `AdventOfCode.PuzzleSolver` module.
|
||||
"""
|
||||
|
||||
use ExUnit.CaseTemplate
|
||||
|
||||
using module: module do
|
||||
quote bind_quoted: [module: module] do
|
||||
@module module
|
||||
|
||||
defp assert_solution(input, desired_output) when is_binary(input) do
|
||||
{:ok, stream_pid} = StringIO.open(input)
|
||||
stream_input = IO.stream(stream_pid, :line)
|
||||
actual_output = AdventOfCode.PuzzleSolver.solve(@module, stream_input)
|
||||
@module.solve(stream_input)
|
||||
assert actual_output == desired_output
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
1
2021/test/test_helper.exs
Normal file
1
2021/test/test_helper.exs
Normal file
|
@ -0,0 +1 @@
|
|||
ExUnit.start()
|
Loading…
Reference in a new issue