Skip to content

Commit

Permalink
2023 day 19, part 2
Browse files Browse the repository at this point in the history
I'm pretty sure I could now write a BFS algorithm in my sleep, I've
done it so many times
  • Loading branch information
sevenseacat committed Dec 19, 2023
1 parent 3aa1074 commit 86038ae
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
My Elixir solutions for [Advent of Code](https://adventofcode.com/) (all years).

<!-- stars start -->
<p><a href="./lib/y2023/"><img src="https://img.shields.io/static/v1?label=2023&message=37%20stars&style=for-the-badge&color=yellow" alt="37 stars" /></a><br />
<p><a href="./lib/y2023/"><img src="https://img.shields.io/static/v1?label=2023&message=38%20stars&style=for-the-badge&color=yellow" alt="38 stars" /></a><br />
<a href="./lib/y2022/"><img src="https://img.shields.io/static/v1?label=2022&message=%E2%AD%90%EF%B8%8F%2050%20stars%20%E2%AD%90%EF%B8%8F&style=for-the-badge&color=brightgreen" alt="50 stars" /></a><br />
<a href="./lib/y2021/"><img src="https://img.shields.io/static/v1?label=2021&message=46%20stars&style=for-the-badge&color=green" alt="46 stars" /></a><br />
<a href="./lib/y2020/"><img src="https://img.shields.io/static/v1?label=2020&message=39%20stars&style=for-the-badge&color=yellow" alt="39 stars" /></a><br />
Expand Down
5 changes: 3 additions & 2 deletions lib/y2023/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

My Elixir solutions for [Advent of Code 2023](https://adventofcode.com/2023).

<!-- stars 2023 start --><img src="https://img.shields.io/static/v1?label=2023&message=37%20stars&style=for-the-badge&color=yellow" alt="37 stars" /><!-- stars 2023 end -->
<!-- stars 2023 start --><img src="https://img.shields.io/static/v1?label=2023&message=38%20stars&style=for-the-badge&color=yellow" alt="38 stars" /><!-- stars 2023 end -->

## Benchmarks

Expand Down Expand Up @@ -51,5 +51,6 @@ day 17, part 1 1.28 782.35 ms ±3.06% 789.96 ms 811.
day 17, part 2 0.30 3345.17 ms ±2.14% 3345.17 ms 3395.69 ms
day 18, part 1 977.01 1.02 ms ±38.28% 0.97 ms 2.78 ms
day 18, part 2 848.64 1.18 ms ±36.31% 1.13 ms 3.13 ms
day 19, part 1 139.97 7.14 ms ±6.39% 7.08 ms 9.33 ms
day 19, part 1 395.99 2.53 ms ±4.59% 2.51 ms 2.81 ms
day 19, part 2 333.72 3.00 ms ±7.48% 2.96 ms 3.40 ms
```
100 changes: 92 additions & 8 deletions lib/y2023/day19.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,97 @@ defmodule Y2023.Day19 do
|> Enum.sum()
end

# @doc """
# iex> Day19.part2("update or delete me")
# "update or delete me"
# """
# def part2(input) do
# input
# end
def part2({workflows, _parts}) do
possibles = %{
"x" => %{min: 1, max: 4000},
"m" => %{min: 1, max: 4000},
"a" => %{min: 1, max: 4000},
"s" => %{min: 1, max: 4000}
}

initial = {"in", 0, possibles}

process_possibles(workflows, {[initial], []}, [])
|> Enum.map(&count_possibles/1)
|> Enum.sum()
end

# Do a breadth-first search exploring whether each rule passes or fails.
defp process_possibles(_workflows, {[], []}, results), do: results

defp process_possibles(workflows, {[], next}, results) do
process_possibles(workflows, {next, []}, results)
end

defp process_possibles(
workflows,
{[{name, index, possibles} | rest], next},
results
) do
case name do
"A" ->
process_possibles(workflows, {rest, next}, [possibles | results])

"R" ->
process_possibles(workflows, {rest, next}, results)

_anything ->
rule =
workflows
|> Map.fetch!(name)
|> Enum.at(index)

# Thankfully there are no loops in the input chain, otherwise we would need to keep track
# of rules checked so far and abort if we see a duplicate
case rule do
string when is_binary(string) ->
process_possibles(
workflows,
{rest, [{string, 0, possibles} | next]},
results
)

{key, op, value, target} ->
# This is a comparison check that can pass or fail - queue up next steps for each.
next_steps =
step_options({key, op, value, target}, {name, index})
|> Enum.map(fn {key, op, value, target, index} ->
possibles = restrict_possibles(possibles, {key, op, value})
{target, index, possibles}
end)

process_possibles(workflows, {rest, next_steps ++ next}, results)
end
end
end

defp step_options({key, :gt, value, target}, {name, index}) do
[
# This rule succeeds - the key must be at least value+1 and the next rule to check will be the one pointed to
{key, :min, value + 1, target, 0},
# This rule fails - the key must be at most value and the next rule to check will be the next for the current workflow
{key, :max, value, name, index + 1}
]
end

# Same as the previous but in reverse
defp step_options({key, :lt, value, target}, {name, index}) do
[{key, :max, value - 1, target, 0}, {key, :min, value, name, index + 1}]
end

# This was previously more exhaustive and checked to see if value was greater
# than or less than the current min/max, in which case updating it might actually
# loosen the range of possibles, but that doesn't come up in my input
defp restrict_possibles(possibles, {key, limit, value}) do
Map.update!(possibles, key, fn map -> %{map | limit => value} end)
end

defp count_possibles(map) do
map
|> Map.values()
|> Enum.map(&(&1.max - &1.min + 1))
|> Enum.product()
end

defp run_workflow(part, workflows, rule_name) do
{:ok, rules} = Map.fetch(workflows, rule_name)
Expand Down Expand Up @@ -85,5 +169,5 @@ defmodule Y2023.Day19 do
end

def part1_verify, do: input() |> parse_input() |> part1()
# def part2_verify, do: input() |> parse_input() |> part2()
def part2_verify, do: input() |> parse_input() |> part2()
end
7 changes: 6 additions & 1 deletion test/y2023/day19_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Y2023.Day19Test do
doctest Day19

test "verification, part 1", do: assert(Day19.part1_verify() == 280_909)
# test "verification, part 2", do: assert(Day19.part2_verify() == "update or delete me")
test "verification, part 2", do: assert(Day19.part2_verify() == 116_138_474_394_508)

@sample_input """
px{a<2006:qkq,m>2090:A,rfg}
Expand All @@ -31,6 +31,11 @@ defmodule Y2023.Day19Test do
assert actual == 19114
end

test "part 2" do
actual = Day19.parse_input(@sample_input) |> Day19.part2()
assert actual == 167_409_079_868_000
end

test "parse_input" do
{workflows, parts} = Day19.parse_input(@sample_input)

Expand Down

0 comments on commit 86038ae

Please sign in to comment.