diff --git a/2015-elixir/README.md b/2015-elixir/README.md index e0b58fa..b535d32 100644 --- a/2015-elixir/README.md +++ b/2015-elixir/README.md @@ -1,8 +1,8 @@ # Aoc -Solutions for [Advent of Code 2020][1], this year in Elixir. +Solutions for [Advent of Code 2021][1], this year in Elixir. -[Elixir 1.11.1][2] preferred. +[Elixir 1.12.3][2] preferred. ## Help scripts diff --git a/2015-elixir/lib/aoc.ex b/2015-elixir/lib/aoc.ex index f20e7a6..56160eb 100644 --- a/2015-elixir/lib/aoc.ex +++ b/2015-elixir/lib/aoc.ex @@ -1,12 +1,9 @@ defmodule Aoc do - @moduledoc """ - Solutions for Advent of Code 2020, written in Elixir 1.11.1. - """ - + @year 2021 def solve_all() do """ - ADVENT OF CODE 2020 + ADVENT OF CODE #{@year} =================== """ |> IO.puts() diff --git a/2015-elixir/lib/mix/tasks/bootstrap.ex b/2015-elixir/lib/mix/tasks/bootstrap.ex index 45a061b..e0ff897 100644 --- a/2015-elixir/lib/mix/tasks/bootstrap.ex +++ b/2015-elixir/lib/mix/tasks/bootstrap.ex @@ -3,6 +3,9 @@ defmodule Mix.Tasks.Aoc.New do @shortdoc "Bootstrap new solution" @impl Mix.Task + + @year 2021 + def run([n, name]) do id = n |> String.pad_leading(2, "0") @@ -17,17 +20,17 @@ defmodule Mix.Tasks.Aoc.New do File.write!(test_file, test_file_content(id)) IO.puts("Creating " <> solution_file) - File.write!(solution_file, solution_file_content(name, id)) + File.write!(solution_file, solution_file_content(name, id, n)) """ \nDone! Start coding. Get your puzzle input here: - https://adventofcode.com/2015/day/#{id}/input + 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 + mix aoc.solve_all -- run all puzzles, starting with 1 + mix aoc.solve #{id} -- run single puzzle, 1-25 """ |> IO.puts() end @@ -48,22 +51,22 @@ defmodule Mix.Tasks.Aoc.New do test "solves first part" do a = "something" |> parse!() |> solve_first_part() - expect a == :something + assert a == :something end test "solves second part" do a = "something" |> parse!() |> solve_second_part() - expect a == :something + assert a == :something end end """ end - defp solution_file_content(name, id) do + defp solution_file_content(name, id, n) do ~s""" defmodule Aoc.Solution.Day#{id} do - @name "#{name}" + @name "Day #{n}: #{name}" @behaviour Solution @impl Solution diff --git a/2015-elixir/lib/solutions/day_06.ex b/2015-elixir/lib/solutions/day_06.ex new file mode 100644 index 0000000..a6a226b --- /dev/null +++ b/2015-elixir/lib/solutions/day_06.ex @@ -0,0 +1,96 @@ +defmodule Aoc.Solution.Day06 do + @name "Day 6: Probably a Fire Hazard" + @behaviour Solution + + @impl Solution + def get_name, do: @name + + @impl Solution + def parse!(raw) do + r = ~r/ ?(\S+) (\d+),(\d+) through (\d+),(\d+)/ + + raw + |> String.split("\n") + |> Enum.map(fn row -> + [_, verb, x1, y1, x2, y2] = Regex.run(r, row) + {verb, String.to_integer(x1)..String.to_integer(x2), String.to_integer(y1)..String.to_integer(y2)} + end) + end + + @impl Solution + def solve_first_part(input) do + lit = MapSet.new() + change_state(lit, input) |> MapSet.size() + end + + @impl Solution + def solve_second_part(input) do + lit = Map.new() + adjust_brightness(lit, input) + end + + defp change_state(lit, []) do + lit + end + + defp change_state(lit, [ {verb, xset, yset} | remaining]) do + change = for x <- xset do + for y <- yset do + {x, y} + end + end |> List.flatten() |> MapSet.new() + case verb do + "on" -> + change_state(MapSet.union(lit, change), remaining) + + "off" -> + change_state(MapSet.difference(lit, change), remaining) + + "toggle" -> + to_lit = MapSet.difference(change, lit) + to_unlit = MapSet.difference(lit, change) + change_state(MapSet.union(to_lit, to_unlit), remaining) + end + end + + defp adjust_brightness(lit, []) do + Map.values(lit) |> Enum.sum + end + + defp adjust_brightness(lit, [ {verb, xset, yset} | remaining]) do + change = for x <- xset do + for y <- yset do + {x, y} + end + end |> List.flatten() + updated = case verb do + "on" -> + Enum.reduce(change, lit, fn xy, acc -> + if Map.has_key?(acc, xy) do + %{acc | xy => Map.get(acc, xy) + 1} + else + Map.put(acc, xy, 1) + end + end) + + "off" -> + Enum.reduce(change, lit, fn xy, acc -> + if Map.has_key?(acc, xy) do + %{acc | xy => max(Map.get(acc, xy) - 1, 0)} + else + acc + end + end) + + "toggle" -> + Enum.reduce(change, lit, fn xy, acc -> + if Map.has_key?(acc, xy) do + %{acc | xy => Map.get(acc, xy) + 2} + else + Map.put(acc, xy, 2) + end + end) + end + adjust_brightness(updated, remaining) + end +end diff --git a/2015-elixir/lib/solutions/day_07.ex b/2015-elixir/lib/solutions/day_07.ex new file mode 100644 index 0000000..abd8bb3 --- /dev/null +++ b/2015-elixir/lib/solutions/day_07.ex @@ -0,0 +1,22 @@ +defmodule Aoc.Solution.Day07 do + @name "Day 7: Some Assembly Required" + @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 diff --git a/2015-elixir/mix.exs b/2015-elixir/mix.exs index 3af1c5c..54f358b 100644 --- a/2015-elixir/mix.exs +++ b/2015-elixir/mix.exs @@ -14,7 +14,7 @@ defmodule Aoc.MixProject do # Run "mix help compile.app" to learn about applications. def application do [ - extra_applications: [:logger, :crypto] + extra_applications: [:logger] ] end diff --git a/2015-elixir/test/day_06_test.exs b/2015-elixir/test/day_06_test.exs new file mode 100644 index 0000000..c93cf05 --- /dev/null +++ b/2015-elixir/test/day_06_test.exs @@ -0,0 +1,45 @@ +defmodule Day06Test do + use ExUnit.Case + doctest Aoc.Solution.Day06 + import Aoc.Solution.Day06 + + test "parses the input" do + input = """ + turn off 674,321 through 793,388 + toggle 749,672 through 973,965 + turn on 943,30 through 990,907 + """ |> String.trim() + + expected = [ + {"off", 674..793, 321..388}, + {"toggle", 749..973, 672..965}, + {"on", 943..990, 30..907}, + ] + + assert parse!(input) == expected + end + + test "solves first part" do + input = """ + turn on 0,0 through 999,999 + toggle 0,0 through 999,0 + turn off 499,499 through 500,500 + """ |> String.trim() + + a = input |> parse!() |> solve_first_part() + + assert a == 998996 + end + + test "solves second part" do + input = """ + turn on 0,0 through 999,999 + toggle 0,0 through 999,0 + turn off 499,499 through 500,500 + """ |> String.trim() + + a = input |> parse!() |> solve_second_part() + + assert a == 1000000 + 2000 - 4 + end +end diff --git a/2015-elixir/test/day_07_test.exs b/2015-elixir/test/day_07_test.exs new file mode 100644 index 0000000..733dd9f --- /dev/null +++ b/2015-elixir/test/day_07_test.exs @@ -0,0 +1,23 @@ +defmodule Day07Test do + use ExUnit.Case + doctest Aoc.Solution.Day07 + import Aoc.Solution.Day07 + + 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 diff --git a/2015-python/README.md b/2015-python/README.md new file mode 100644 index 0000000..8715ad1 --- /dev/null +++ b/2015-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/2015-python/aoc.py b/2015-python/aoc.py new file mode 100644 index 0000000..aae6d95 --- /dev/null +++ b/2015-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/2015-python/solutions/__init__.py b/2015-python/solutions/__init__.py new file mode 100644 index 0000000..3810ab4 --- /dev/null +++ b/2015-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/2015-python/solutions/day_06.py b/2015-python/solutions/day_06.py new file mode 100644 index 0000000..f5ccd53 --- /dev/null +++ b/2015-python/solutions/day_06.py @@ -0,0 +1,22 @@ +from solutions import BaseSolution + + +class Solution(BaseSolution): + input_file = "06.txt" + + def __str__(self): + return "Day 6: Probably a Fire Hazard" + + 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() diff --git a/2015-python/tests/__init__.py b/2015-python/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/2015-python/tests/test_day_06.py b/2015-python/tests/test_day_06.py new file mode 100644 index 0000000..aeb9aef --- /dev/null +++ b/2015-python/tests/test_day_06.py @@ -0,0 +1,29 @@ +import unittest + +from solutions.day_06 import Solution + + +class Day06TestCase(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()