Solve 2015:7 "Some Assembly Required"

With all new bootstrap script
This commit is contained in:
Anders Englöf Ytterström 2022-11-26 00:05:55 +01:00 committed by Anders Englöf Ytterström
parent 102982a6f0
commit db91df6cfc
14 changed files with 498 additions and 0 deletions

View 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
View 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
View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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

View file

@ -0,0 +1,5 @@
defmodule AocTest do
use ExUnit.Case
doctest Aoc
doctest Aoc.Utils
end

View 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

View file

@ -0,0 +1 @@
ExUnit.start()