From d5a29d41e287d1917e944ce6e45acb3d17cfea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Fri, 22 Oct 2021 15:58:27 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20Setup=20Advent=20of=20Code=20202?= =?UTF-8?q?1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This year: Elixir! And maybe python. --- 2021-elixir/.formatter.exs | 4 + 2021-elixir/.gitignore | 26 ++++++ 2021-elixir/.tool-versions | 1 + 2021-elixir/README.md | 29 +++++++ 2021-elixir/lib/aoc.ex | 58 +++++++++++++ 2021-elixir/lib/mix/tasks/bootstrap.ex | 92 ++++++++++++++++++++ 2021-elixir/lib/mix/tasks/solve.ex | 11 +++ 2021-elixir/lib/mix/tasks/solve_all.ex | 9 ++ 2021-elixir/lib/solution.ex | 6 ++ 2021-elixir/mix.exs | 28 ++++++ 2021-elixir/test/aoc_test.exs | 4 + 2021-elixir/test/test_helper.exs | 1 + 2021-python/.gitignore | 4 + 2021-python/.python-version | 1 + 2021-python/README.md | 29 +++++++ 2021-python/aoc.py | 113 +++++++++++++++++++++++++ 2021-python/solutions/__init__.py | 32 +++++++ 2021-python/tests/__init__.py | 0 18 files changed, 448 insertions(+) create mode 100644 2021-elixir/.formatter.exs create mode 100644 2021-elixir/.gitignore create mode 100644 2021-elixir/.tool-versions create mode 100644 2021-elixir/README.md create mode 100644 2021-elixir/lib/aoc.ex create mode 100644 2021-elixir/lib/mix/tasks/bootstrap.ex create mode 100644 2021-elixir/lib/mix/tasks/solve.ex create mode 100644 2021-elixir/lib/mix/tasks/solve_all.ex create mode 100644 2021-elixir/lib/solution.ex create mode 100644 2021-elixir/mix.exs create mode 100644 2021-elixir/test/aoc_test.exs create mode 100644 2021-elixir/test/test_helper.exs create mode 100644 2021-python/.gitignore create mode 100644 2021-python/.python-version create mode 100644 2021-python/README.md create mode 100644 2021-python/aoc.py create mode 100644 2021-python/solutions/__init__.py create mode 100644 2021-python/tests/__init__.py diff --git a/2021-elixir/.formatter.exs b/2021-elixir/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/2021-elixir/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/2021-elixir/.gitignore b/2021-elixir/.gitignore new file mode 100644 index 0000000..c203d4c --- /dev/null +++ b/2021-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 + +# Ignore ElixirLS VS Code extension +.elixir_ls \ No newline at end of file diff --git a/2021-elixir/.tool-versions b/2021-elixir/.tool-versions new file mode 100644 index 0000000..53fa83f --- /dev/null +++ b/2021-elixir/.tool-versions @@ -0,0 +1 @@ +elixir 1.12.0 diff --git a/2021-elixir/README.md b/2021-elixir/README.md new file mode 100644 index 0000000..2b5607e --- /dev/null +++ b/2021-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/2021-elixir/lib/aoc.ex b/2021-elixir/lib/aoc.ex new file mode 100644 index 0000000..56160eb --- /dev/null +++ b/2021-elixir/lib/aoc.ex @@ -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 diff --git a/2021-elixir/lib/mix/tasks/bootstrap.ex b/2021-elixir/lib/mix/tasks/bootstrap.ex new file mode 100644 index 0000000..e0ff897 --- /dev/null +++ b/2021-elixir/lib/mix/tasks/bootstrap.ex @@ -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 diff --git a/2021-elixir/lib/mix/tasks/solve.ex b/2021-elixir/lib/mix/tasks/solve.ex new file mode 100644 index 0000000..9cdd082 --- /dev/null +++ b/2021-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/2021-elixir/lib/mix/tasks/solve_all.ex b/2021-elixir/lib/mix/tasks/solve_all.ex new file mode 100644 index 0000000..7fd5a6d --- /dev/null +++ b/2021-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/2021-elixir/lib/solution.ex b/2021-elixir/lib/solution.ex new file mode 100644 index 0000000..d1f8d8f --- /dev/null +++ b/2021-elixir/lib/solution.ex @@ -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 diff --git a/2021-elixir/mix.exs b/2021-elixir/mix.exs new file mode 100644 index 0000000..54f358b --- /dev/null +++ b/2021-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.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 diff --git a/2021-elixir/test/aoc_test.exs b/2021-elixir/test/aoc_test.exs new file mode 100644 index 0000000..659caac --- /dev/null +++ b/2021-elixir/test/aoc_test.exs @@ -0,0 +1,4 @@ +defmodule AocTest do + use ExUnit.Case + doctest Aoc +end diff --git a/2021-elixir/test/test_helper.exs b/2021-elixir/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/2021-elixir/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/2021-python/.gitignore b/2021-python/.gitignore new file mode 100644 index 0000000..c8a405d --- /dev/null +++ b/2021-python/.gitignore @@ -0,0 +1,4 @@ +.idea +*.pyc +__pycache__ +.vscode diff --git a/2021-python/.python-version b/2021-python/.python-version new file mode 100644 index 0000000..1981190 --- /dev/null +++ b/2021-python/.python-version @@ -0,0 +1 @@ +3.8.0 diff --git a/2021-python/README.md b/2021-python/README.md new file mode 100644 index 0000000..8715ad1 --- /dev/null +++ b/2021-python/README.md @@ -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/.txt`, `solutions/day_.py` och +`tests/test_day_.py`): + + python aoc.py "" + +Manually copy the puzzle input from https://adventofcode.com and paste it in `inputs/.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 \ No newline at end of file diff --git a/2021-python/aoc.py b/2021-python/aoc.py new file mode 100644 index 0000000..aae6d95 --- /dev/null +++ b/2021-python/aoc.py @@ -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( + \"\"\" + + \"\"\" + ) + + def test_parse_puzzle_input(self): + data = \"\"\" + + \"\"\" + assert self.solution.parse_input(data) == "" + + # 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 diff --git a/2021-python/solutions/__init__.py b/2021-python/solutions/__init__.py new file mode 100644 index 0000000..3810ab4 --- /dev/null +++ b/2021-python/solutions/__init__.py @@ -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 diff --git a/2021-python/tests/__init__.py b/2021-python/tests/__init__.py new file mode 100644 index 0000000..e69de29