From 5929ae11f6beb37de953521257f36f4e1fa2f554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Fri, 25 Nov 2022 12:01:51 +0100 Subject: [PATCH] Prepare for Advent of Code 2022 --- 2022-elixir/.formatter.exs | 4 + 2022-elixir/.gitignore | 26 ++++++ 2022-elixir/README.md | 29 +++++++ 2022-elixir/lib/aoc.ex | 60 ++++++++++++++ 2022-elixir/lib/mix/tasks/bootstrap.ex | 109 +++++++++++++++++++++++++ 2022-elixir/lib/mix/tasks/solve.ex | 11 +++ 2022-elixir/lib/mix/tasks/solve_all.ex | 9 ++ 2022-elixir/lib/solution.ex | 8 ++ 2022-elixir/lib/utils.ex | 65 +++++++++++++++ 2022-elixir/mix.exs | 28 +++++++ 2022-elixir/test/aoc_test.exs | 5 ++ 2022-elixir/test/test_helper.exs | 1 + 12 files changed, 355 insertions(+) create mode 100644 2022-elixir/.formatter.exs create mode 100644 2022-elixir/.gitignore create mode 100644 2022-elixir/README.md create mode 100644 2022-elixir/lib/aoc.ex create mode 100644 2022-elixir/lib/mix/tasks/bootstrap.ex create mode 100644 2022-elixir/lib/mix/tasks/solve.ex create mode 100644 2022-elixir/lib/mix/tasks/solve_all.ex create mode 100644 2022-elixir/lib/solution.ex create mode 100644 2022-elixir/lib/utils.ex create mode 100644 2022-elixir/mix.exs create mode 100644 2022-elixir/test/aoc_test.exs create mode 100644 2022-elixir/test/test_helper.exs diff --git a/2022-elixir/.formatter.exs b/2022-elixir/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/2022-elixir/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/2022-elixir/.gitignore b/2022-elixir/.gitignore new file mode 100644 index 0000000..e23ab19 --- /dev/null +++ b/2022-elixir/.gitignore @@ -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/ diff --git a/2022-elixir/README.md b/2022-elixir/README.md new file mode 100644 index 0000000..2b5607e --- /dev/null +++ b/2022-elixir/README.md @@ -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 \ No newline at end of file diff --git a/2022-elixir/lib/aoc.ex b/2022-elixir/lib/aoc.ex new file mode 100644 index 0000000..d92fb47 --- /dev/null +++ b/2022-elixir/lib/aoc.ex @@ -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 diff --git a/2022-elixir/lib/mix/tasks/bootstrap.ex b/2022-elixir/lib/mix/tasks/bootstrap.ex new file mode 100644 index 0000000..4216587 --- /dev/null +++ b/2022-elixir/lib/mix/tasks/bootstrap.ex @@ -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 diff --git a/2022-elixir/lib/mix/tasks/solve.ex b/2022-elixir/lib/mix/tasks/solve.ex new file mode 100644 index 0000000..9cdd082 --- /dev/null +++ b/2022-elixir/lib/mix/tasks/solve.ex @@ -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 diff --git a/2022-elixir/lib/mix/tasks/solve_all.ex b/2022-elixir/lib/mix/tasks/solve_all.ex new file mode 100644 index 0000000..7fd5a6d --- /dev/null +++ b/2022-elixir/lib/mix/tasks/solve_all.ex @@ -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 diff --git a/2022-elixir/lib/solution.ex b/2022-elixir/lib/solution.ex new file mode 100644 index 0000000..1f6bd63 --- /dev/null +++ b/2022-elixir/lib/solution.ex @@ -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 diff --git a/2022-elixir/lib/utils.ex b/2022-elixir/lib/utils.ex new file mode 100644 index 0000000..4c06fe4 --- /dev/null +++ b/2022-elixir/lib/utils.ex @@ -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 diff --git a/2022-elixir/mix.exs b/2022-elixir/mix.exs new file mode 100644 index 0000000..83ea864 --- /dev/null +++ b/2022-elixir/mix.exs @@ -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 diff --git a/2022-elixir/test/aoc_test.exs b/2022-elixir/test/aoc_test.exs new file mode 100644 index 0000000..3a46923 --- /dev/null +++ b/2022-elixir/test/aoc_test.exs @@ -0,0 +1,5 @@ +defmodule AocTest do + use ExUnit.Case + doctest Aoc + doctest Aoc.Utils +end diff --git a/2022-elixir/test/test_helper.exs b/2022-elixir/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/2022-elixir/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()