Solve 2015:7 "Some Assembly Required"
With all new bootstrap script
This commit is contained in:
parent
102982a6f0
commit
db91df6cfc
14 changed files with 498 additions and 0 deletions
4
2015-elixir-2/.formatter.exs
Normal file
4
2015-elixir-2/.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
||||||
26
2015-elixir-2/.gitignore
vendored
Normal file
26
2015-elixir-2/.gitignore
vendored
Normal 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
2015-elixir-2/README.md
Normal file
29
2015-elixir-2/README.md
Normal 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
|
||||||
58
2015-elixir-2/lib/aoc.ex
Normal file
58
2015-elixir-2/lib/aoc.ex
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
defmodule Aoc do
|
||||||
|
@year 2015
|
||||||
|
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])
|
||||||
|
|
||||||
|
[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
|
||||||
101
2015-elixir-2/lib/mix/tasks/bootstrap.ex
Normal file
101
2015-elixir-2/lib/mix/tasks/bootstrap.ex
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
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_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: if(not 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
|
||||||
|
@name "Day #{n}: #{name}"
|
||||||
|
@behaviour Solution
|
||||||
|
|
||||||
|
@impl Solution
|
||||||
|
def get_name, do: @name
|
||||||
|
|
||||||
|
@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
|
||||||
11
2015-elixir-2/lib/mix/tasks/solve.ex
Normal file
11
2015-elixir-2/lib/mix/tasks/solve.ex
Normal 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
|
||||||
9
2015-elixir-2/lib/mix/tasks/solve_all.ex
Normal file
9
2015-elixir-2/lib/mix/tasks/solve_all.ex
Normal 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
|
||||||
6
2015-elixir-2/lib/solution.ex
Normal file
6
2015-elixir-2/lib/solution.ex
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
defmodule Solution do
|
||||||
|
@callback parse!(String.t()) :: Any
|
||||||
|
@callback get_name() :: String.t()
|
||||||
|
@callback solve(Any) :: String.t()
|
||||||
|
@callback solve_again(Any) :: String.t()
|
||||||
|
end
|
||||||
122
2015-elixir-2/lib/solutions/day_07.ex
Normal file
122
2015-elixir-2/lib/solutions/day_07.ex
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
defmodule Aoc.Solution.Day07 do
|
||||||
|
import Bitwise
|
||||||
|
import Aoc.Utils
|
||||||
|
|
||||||
|
@name "Day 7: Some Assembly Required"
|
||||||
|
@behaviour Solution
|
||||||
|
|
||||||
|
@impl Solution
|
||||||
|
def get_name, do: @name
|
||||||
|
|
||||||
|
@impl Solution
|
||||||
|
def parse!(raw) do
|
||||||
|
split_lines(raw)
|
||||||
|
|> Enum.map(fn l -> parse_values(l, " -> ") end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Solution
|
||||||
|
def solve(input, k \\ "a") do
|
||||||
|
wires = Map.new()
|
||||||
|
all = Enum.count(input)
|
||||||
|
wire(wires, all, input) |> Map.get(k)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Solution
|
||||||
|
def solve_again(input) do
|
||||||
|
value = solve(input)
|
||||||
|
|
||||||
|
solve(
|
||||||
|
Enum.map(
|
||||||
|
input,
|
||||||
|
fn [o, i] ->
|
||||||
|
case i do
|
||||||
|
"b" -> [Integer.to_string(value), i]
|
||||||
|
_ -> [o, i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def wire(wires, all, queue) do
|
||||||
|
wires = walk(wires, queue)
|
||||||
|
|
||||||
|
case Enum.count(wires) do
|
||||||
|
^all -> wires
|
||||||
|
_ -> wire(wires, all, queue)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def walk(wires, []), do: wires
|
||||||
|
|
||||||
|
def walk(wires, [[output, input] | remain]) do
|
||||||
|
case apply_rule(wires, String.split(output)) do
|
||||||
|
nil ->
|
||||||
|
walk(wires, remain)
|
||||||
|
|
||||||
|
v ->
|
||||||
|
walk(
|
||||||
|
Map.put_new(
|
||||||
|
wires,
|
||||||
|
input,
|
||||||
|
v
|
||||||
|
),
|
||||||
|
remain
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_rule(wires, ["NOT", wire]) do
|
||||||
|
case Map.get(wires, wire) do
|
||||||
|
nil -> nil
|
||||||
|
v -> 65536 + bnot(v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_rule(wires, [l, "AND", r]) do
|
||||||
|
l = _value(wires, l)
|
||||||
|
r = _value(wires, r)
|
||||||
|
case Enum.all?([l, r]) do
|
||||||
|
false -> nil
|
||||||
|
true -> l &&& r
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_rule(wires, [l, "OR", r]) do
|
||||||
|
l = _value(wires, l)
|
||||||
|
r = _value(wires, r)
|
||||||
|
case Enum.all?([l, r]) do
|
||||||
|
false -> nil
|
||||||
|
true -> bor(l, r)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_rule(wires, [l, "RSHIFT", r]) do
|
||||||
|
case Map.get(wires, l) do
|
||||||
|
nil -> nil
|
||||||
|
l -> l >>> String.to_integer(r)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_rule(wires, [l, "LSHIFT", r]) do
|
||||||
|
case Map.get(wires, l) do
|
||||||
|
nil -> nil
|
||||||
|
l -> l <<< String.to_integer(r)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_rule(wires, [wire]) do
|
||||||
|
_value(wires, wire)
|
||||||
|
end
|
||||||
|
|
||||||
|
def _value(seen, k) do
|
||||||
|
try do
|
||||||
|
case Map.get(seen, k) do
|
||||||
|
nil -> String.to_integer(k)
|
||||||
|
v -> v
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
_e -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
65
2015-elixir-2/lib/utils.ex
Normal file
65
2015-elixir-2/lib/utils.ex
Normal 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
2015-elixir-2/mix.exs
Normal file
28
2015-elixir-2/mix.exs
Normal 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
|
||||||
5
2015-elixir-2/test/aoc_test.exs
Normal file
5
2015-elixir-2/test/aoc_test.exs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
defmodule AocTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest Aoc
|
||||||
|
doctest Aoc.Utils
|
||||||
|
end
|
||||||
33
2015-elixir-2/test/solutions/day_07_test.exs
Normal file
33
2015-elixir-2/test/solutions/day_07_test.exs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
defmodule Day07Test do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest Aoc.Solution.Day07
|
||||||
|
import Aoc.Solution.Day07
|
||||||
|
|
||||||
|
@input ~s(
|
||||||
|
123 -> x
|
||||||
|
456 -> y
|
||||||
|
x AND y -> d
|
||||||
|
x OR y -> e
|
||||||
|
x LSHIFT 2 -> f
|
||||||
|
y RSHIFT 2 -> g
|
||||||
|
NOT x -> h
|
||||||
|
NOT y -> i
|
||||||
|
)
|
||||||
|
|
||||||
|
test "07: Some Assembly Required, part 1" do
|
||||||
|
expected = %{
|
||||||
|
"d" => 72,
|
||||||
|
"e" => 507,
|
||||||
|
"f" => 492,
|
||||||
|
"g" => 114,
|
||||||
|
"h" => 65412,
|
||||||
|
"i" => 65079,
|
||||||
|
"x" => 123,
|
||||||
|
"y" => 456
|
||||||
|
}
|
||||||
|
|
||||||
|
result = @input |> parse!() |> solve("d")
|
||||||
|
|
||||||
|
assert result == Map.get(expected, "d")
|
||||||
|
end
|
||||||
|
end
|
||||||
1
2015-elixir-2/test/test_helper.exs
Normal file
1
2015-elixir-2/test/test_helper.exs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ExUnit.start()
|
||||||
Loading…
Add table
Reference in a new issue