diff --git a/2021/.formatter.exs b/2021/.formatter.exs index d2cda26..fea6a6c 100644 --- a/2021/.formatter.exs +++ b/2021/.formatter.exs @@ -1,4 +1,5 @@ # Used by "mix format" [ - inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], + locals_without_parens: [assert_solution: 2] ] diff --git a/2021/lib/advent_of_code.ex b/2021/lib/advent_of_code.ex index 6b37c41..f8016fb 100644 --- a/2021/lib/advent_of_code.ex +++ b/2021/lib/advent_of_code.ex @@ -1,5 +1,12 @@ defmodule AdventOfCode do @moduledoc """ - Documentation for `AdventOfCode`. + 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 diff --git a/2021/lib/advent_of_code/day0.ex b/2021/lib/advent_of_code/day0.ex new file mode 100644 index 0000000..e69de29 diff --git a/2021/lib/advent_of_code/day0/part0.ex b/2021/lib/advent_of_code/day0/part0.ex new file mode 100644 index 0000000..6c32387 --- /dev/null +++ b/2021/lib/advent_of_code/day0/part0.ex @@ -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 diff --git a/2021/lib/advent_of_code/puzzle_solver.ex b/2021/lib/advent_of_code/puzzle_solver.ex index f79f334..212df08 100644 --- a/2021/lib/advent_of_code/puzzle_solver.ex +++ b/2021/lib/advent_of_code/puzzle_solver.ex @@ -6,7 +6,9 @@ defmodule AdventOfCode.PuzzleSolver do @doc """ Given the input as a stream, return the solution as a string """ - @callback solve(IO.Stream.t()) :: String.t() + @callback solve(Enumerable.t()) :: String.t() + + def solve(mod, stream), do: apply(mod, :solve, [stream]) defmacro __using__(_) do quote do diff --git a/2021/lib/mix/tasks/advent_of_code.gen.solution.exs b/2021/lib/mix/tasks/advent_of_code.gen.solution.ex similarity index 100% rename from 2021/lib/mix/tasks/advent_of_code.gen.solution.exs rename to 2021/lib/mix/tasks/advent_of_code.gen.solution.ex diff --git a/2021/lib/mix/tasks/advent_of_code.solve.ex b/2021/lib/mix/tasks/advent_of_code.solve.ex new file mode 100644 index 0000000..5c506c5 --- /dev/null +++ b/2021/lib/mix/tasks/advent_of_code.solve.ex @@ -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 diff --git a/2021/lib/mix/tasks/advent_of_code.solve.exs b/2021/lib/mix/tasks/advent_of_code.solve.exs deleted file mode 100644 index 645e550..0000000 --- a/2021/lib/mix/tasks/advent_of_code.solve.exs +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Mix.Tasks.AdventOfCode.Solve do - use Mix.Task - - @shortdoc "Runs solution code with problem input" - - @moduledoc """ - #{@shortdoc}. - - ## Examples - - # Run Day 2, Part 1 solution program - $ mix advent_of_code.solve 2.1 - """ - - @impl Mix.Task - def run(_args) do - end -end diff --git a/2021/mix.exs b/2021/mix.exs index 949a228..99a37cc 100644 --- a/2021/mix.exs +++ b/2021/mix.exs @@ -7,6 +7,7 @@ defmodule AdventOfCode.MixProject do version: "0.1.0", elixir: "~> 1.11", start_permanent: Mix.env() == :prod, + elixirc_paths: elixirc_paths(Mix.env()), deps: deps() ] end @@ -18,11 +19,13 @@ defmodule AdventOfCode.MixProject do ] 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 [ - # {:dep_from_hexpm, "~> 0.3.0"}, - # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + {:progress_bar, "~> 2.0"} ] end end diff --git a/2021/mix.lock b/2021/mix.lock new file mode 100644 index 0000000..e43918e --- /dev/null +++ b/2021/mix.lock @@ -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"}, +} diff --git a/2021/priv/inputs/0.0.input b/2021/priv/inputs/0.0.input new file mode 100644 index 0000000..86e041d --- /dev/null +++ b/2021/priv/inputs/0.0.input @@ -0,0 +1,3 @@ +foo +bar +baz diff --git a/2021/test/advent_of_code/day0/part_0_test.exs b/2021/test/advent_of_code/day0/part_0_test.exs new file mode 100644 index 0000000..cd53141 --- /dev/null +++ b/2021/test/advent_of_code/day0/part_0_test.exs @@ -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 diff --git a/2021/test/advent_of_code_test.exs b/2021/test/advent_of_code_test.exs new file mode 100644 index 0000000..589fa60 --- /dev/null +++ b/2021/test/advent_of_code_test.exs @@ -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 diff --git a/2021/test/support/puzzle_case.ex b/2021/test/support/puzzle_case.ex index 3d51249..c0d7e09 100644 --- a/2021/test/support/puzzle_case.ex +++ b/2021/test/support/puzzle_case.ex @@ -9,8 +9,11 @@ defmodule AdventOfCode.PuzzleCase do quote bind_quoted: [module: module] do @module module - defp assert_solution(input, desired_output) do - actual_output = @module.run(input) + 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