Skip to content

Commit

Permalink
2023 day 20, part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
sevenseacat committed Dec 20, 2023
1 parent 86038ae commit 5cf8518
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 2 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=38%20stars&style=for-the-badge&color=yellow" alt="38 stars" /></a><br />
<p><a href="./lib/y2023/"><img src="https://img.shields.io/static/v1?label=2023&message=39%20stars&style=for-the-badge&color=yellow" alt="39 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
3 changes: 2 additions & 1 deletion 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=38%20stars&style=for-the-badge&color=yellow" alt="38 stars" /><!-- stars 2023 end -->
<!-- stars 2023 start --><img src="https://img.shields.io/static/v1?label=2023&message=39%20stars&style=for-the-badge&color=yellow" alt="39 stars" /><!-- stars 2023 end -->

## Benchmarks

Expand Down Expand Up @@ -53,4 +53,5 @@ day 18, part 1 977.01 1.02 ms ±38.28% 0.97 ms 2.
day 18, part 2 848.64 1.18 ms ±36.31% 1.13 ms 3.13 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
day 20, part 1 82.10 12.18 ms ±4.31% 12.14 ms 12.89 ms
```
124 changes: 124 additions & 0 deletions lib/y2023/day20.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
defmodule Y2023.Day20 do
use Advent.Day, no: 20

def part1(modules, count \\ 1000) do
Enum.reduce(1..count, {modules, %{high: 0, low: 0}}, fn _count, {modules, pulses} ->
:queue.new()
|> add_to_queue([{"broadcaster", :low, :button}])
|> send_pulse(modules, pulses)
end)
|> elem(1)
end

# @doc """
# iex> Day20.part2("update or delete me")
# "update or delete me"
# """
# def part2(input) do
# input
# end

defp add_to_queue(queue, items) do
Enum.reduce(items, queue, fn item, queue ->
:queue.in(item, queue)
end)
end

defp send_pulse(queue, modules, pulses) do
do_send_pulse(:queue.out(queue), modules, pulses)
end

defp do_send_pulse({:empty, _queue}, modules, pulses), do: {modules, pulses}

defp do_send_pulse({{:value, {to_name, type, from_name}}, queue}, modules, pulses) do
# IO.puts("#{from_name} --#{type}--> #{to_name}")
pulses = Map.update!(pulses, type, &(&1 + 1))

case Map.fetch(modules, to_name) do
:error ->
# "testing purposes"
send_pulse(queue, modules, pulses)

{:ok, to} ->
case to.type do
:forward ->
queue = add_to_queue(queue, Enum.map(to.outputs, &{&1, type, to_name}))
send_pulse(queue, modules, pulses)

:flipflop ->
if type == :high do
# Nothing happens
send_pulse(queue, modules, pulses)
else
to = Map.put(to, :status, toggle(to.status))
modules = Map.put(modules, to_name, to)

queue =
add_to_queue(queue, Enum.map(to.outputs, &{&1, pulse_type(to.status), to_name}))

send_pulse(queue, modules, pulses)
end

:conjunction ->
to = Map.update!(to, :received, fn r -> Map.replace!(r, from_name, type) end)

next_pulse =
if Enum.all?(to.received, fn {_name, last} -> last == :high end) do
:low
else
:high
end

modules = Map.put(modules, to_name, to)
queue = add_to_queue(queue, Enum.map(to.outputs, &{&1, next_pulse, to_name}))

send_pulse(queue, modules, pulses)
end
end
end

defp toggle(:on), do: :off
defp toggle(:off), do: :on

defp pulse_type(:on), do: :high
defp pulse_type(:off), do: :low

def parse_input(input) do
output =
for row <- String.split(input, "\n", trim: true), into: %{} do
[name | outputs] = String.split(row, [" -> ", ", "])

{name, type, extra} =
case String.first(name) do
"%" ->
{String.slice(name, 1..100), :flipflop, %{status: :off}}

"&" ->
{String.slice(name, 1..100), :conjunction, %{}}

_ ->
{name, :forward, %{}}
end

{name, Map.merge(%{type: type, outputs: outputs}, extra)}
end

# Need to do another pass to record inputs for conjunction modules
conjunctions =
output
|> Enum.filter(fn {_name, %{type: type}} -> type == :conjunction end)
|> Enum.map(fn {name, data} ->
in_data =
Enum.filter(output, fn {_, %{outputs: outputs}} -> name in outputs end)
|> Enum.map(fn {name, _} -> {name, :low} end)

{name, Map.put(data, :received, Map.new(in_data))}
end)
|> Map.new()

Map.merge(output, conjunctions)
end

def part1_verify, do: input() |> parse_input() |> part1() |> Map.values() |> Enum.product()
# def part2_verify, do: input() |> parse_input() |> part2()
end
Binary file added lib/y2023/input/day20.txt
Binary file not shown.
70 changes: 70 additions & 0 deletions test/y2023/day20_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
defmodule Y2023.Day20Test do
use ExUnit.Case, async: true
alias Y2023.Day20
doctest Day20

test "verification, part 1", do: assert(Day20.part1_verify() == 949_764_474)
# test "verification, part 2", do: assert(Day20.part2_verify() == "update or delete me")

@sample_1_input """
broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a
"""

@sample_2_input """
broadcaster -> a
%a -> inv, con
&inv -> b
%b -> con
&con -> output
"""

describe "parse input" do
test "sample 1" do
actual = Day20.parse_input(@sample_1_input)

expected = %{
"broadcaster" => %{type: :forward, outputs: ["a", "b", "c"]},
"a" => %{type: :flipflop, outputs: ["b"], status: :off},
"b" => %{type: :flipflop, outputs: ["c"], status: :off},
"c" => %{type: :flipflop, outputs: ["inv"], status: :off},
"inv" => %{type: :conjunction, outputs: ["a"], received: %{"c" => :low}}
}

assert actual == expected
end

test "sample 2" do
actual = Day20.parse_input(@sample_2_input)

expected = %{
"broadcaster" => %{type: :forward, outputs: ["a"]},
"a" => %{type: :flipflop, outputs: ["inv", "con"], status: :off},
"inv" => %{type: :conjunction, outputs: ["b"], received: %{"a" => :low}},
"b" => %{type: :flipflop, outputs: ["con"], status: :off},
"con" => %{type: :conjunction, outputs: ["output"], received: %{"a" => :low, "b" => :low}}
}

assert actual == expected
end
end

describe "part 1" do
test "sample 1" do
assert Day20.parse_input(@sample_1_input) |> Day20.part1() == %{low: 8000, high: 4000}
end

test "sample 2" do
input = Day20.parse_input(@sample_2_input)

assert Day20.part1(input, 1) == %{low: 4, high: 4}
assert Day20.part1(input, 2) == %{low: 8, high: 6}
assert Day20.part1(input, 3) == %{low: 13, high: 9}
assert Day20.part1(input, 4) == %{low: 17, high: 11}
assert Day20.part1(input) == %{low: 4250, high: 2750}
end
end
end

0 comments on commit 5cf8518

Please sign in to comment.