Prepare for Advent of Code 2022

This commit is contained in:
Anders Englöf Ytterström 2022-11-25 12:01:51 +01:00 committed by Anders Englöf Ytterström
parent 5ed6e5e8ce
commit 5929ae11f6
12 changed files with 355 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

26
2022-elixir/.gitignore vendored Normal file
View 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
# Temporary files, for example, from tests.
/tmp/

29
2022-elixir/README.md Normal file
View 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

60
2022-elixir/lib/aoc.ex Normal file
View file

@ -0,0 +1,60 @@
defmodule Aoc do
@year 2022
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, [data])
second_solution = apply(solution_module, :solve_again, [data])
a = apply(solution_module, :present, [first_solution])
b = apply(solution_module, :present_again, [second_solution])
[name, a, b]
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

View file

@ -0,0 +1,109 @@
defmodule Mix.Tasks.Aoc.New do
use Mix.Task
@shortdoc "Bootstrap new solution"
@impl Mix.Task
@year 2022
def run([n, name]) do
id = n |> String.pad_leading(2, "0")
input_dir = "./inputs"
solutions_dir = "./lib/solutions"
tests_dir = "./test/solutions"
input_file = input_dir <> "/" <> id <> ".in"
test_file = tests_dir <> "/day_" <> id <> "_test.exs"
solution_file = solutions_dir <> "/day_" <> id <> ".ex"
for dir_path <- [input_dir, tests_dir, solutions_dir],
do: unless(File.exists?(dir_path), do: File.mkdir(dir_path))
IO.puts("Creating " <> input_file)
File.touch(input_file)
IO.puts("Creating " <> test_file)
File.write!(test_file, test_file_content(id, name))
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, name) do
"""
defmodule Day#{id}Test do
use ExUnit.Case
doctest Aoc.Solution.Day#{id}
import Aoc.Solution.Day#{id}
@input ~s(
REPLACE ME
)
test "#{id}: #{name}, part 1" do
expected = :something
result = @input |> parse!() |> solve()
assert result == expected
end
# test "#{id}: #{name}, part 2" do
# expected = :something
# result = @input |> parse!() |> solve_again()
# assert result == expected
# end
end
"""
end
defp solution_file_content(name, id, n) do
~s"""
defmodule Aoc.Solution.Day#{id} do
import Aoc.Utils
@name "Day #{n}: #{name}"
@behaviour Solution
@impl Solution
def get_name, do: @name
@impl Solution
def present(solution), do: "Solution is \#{solution}"
@impl Solution
def present_again(solution), do: "Solution is \#{solution}"
@impl Solution
def parse!(raw) do
raw
end
@impl Solution
def solve(_input) do
"(TBW)"
end
@impl Solution
def solve_again(_input) do
"(TBW)"
end
end
"""
end
end

View 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

View 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

View file

@ -0,0 +1,8 @@
defmodule Solution do
@callback parse!(String.t()) :: Any
@callback get_name() :: String.t()
@callback solve(Any) :: String.t()
@callback solve_again(Any) :: String.t()
@callback present(Any) :: String.t()
@callback present_again(Any) :: String.t()
end

65
2022-elixir/lib/utils.ex Normal file
View file

@ -0,0 +1,65 @@
defmodule Aoc.Utils do
@doc """
Extracts values as trimmed strings using a separator.
## Examples
iex> Aoc.Utils.parse_values(~s(
...> abc:
...> 123 q1w2e3r4
...> ), ":")
["abc", "123 q1w2e3r4"]
iex> Aoc.Utils.parse_values(~s(
...> abc AND 123 AND q1w2e3r4
...> ), "AND")
["abc", "123", "q1w2e3r4"]
"""
def parse_values(input, separator) do
input
|> String.trim()
|> String.split(separator)
|> Enum.map(&String.trim/1)
end
@doc """
Extracts values from input by white space as trimmed strings.
## Examples
iex> Aoc.Utils.parse_values(~s(
...> abc
...> 123 q1w2e3r4
...> ))
["abc", "123", "q1w2e3r4"]
"""
def parse_values(input), do: String.split(input)
@doc """
Split input to trimmed lines.
Equal to parse_values(input, ["\n", "\r"])
## Examples
iex> Aoc.Utils.split_lines(~s(
...> a bc
...> 12 3
...> q1w2 e3r4
...> ))
["a bc", "12 3", "q1w2 e3r4"]
"""
def split_lines(input), do: parse_values(input, ["\n", "\r"])
@doc """
Split input to trimmed strings by comma as separator.
Equal to Aoc.Utils.parse_values(input, ",")
## Examples
iex> Aoc.Utils.parse_csv(~s(
...> abc,123,q1w2 e3r4
...> ))
["abc", "123", "q1w2 e3r4"]
"""
def parse_csv(input), do: parse_values(input, ",")
end

28
2022-elixir/mix.exs Normal file
View file

@ -0,0 +1,28 @@
defmodule Aoc.MixProject do
use Mix.Project
def project do
[
app: :aoc,
version: "0.1.0",
elixir: "~> 1.14",
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

View file

@ -0,0 +1,5 @@
defmodule AocTest do
use ExUnit.Case
doctest Aoc
doctest Aoc.Utils
end

View file

@ -0,0 +1 @@
ExUnit.start()