Watch
1
0
Fork
You've already forked advent-of-code
0

2023 Day 20 Part 1

This commit is contained in:
Jeremy Kaplan 2023-12-20 20:31:37 -08:00
commit 680e168478

210
2023/day20.rb Normal file
View file

@ -0,0 +1,210 @@
# frozen_string_literal: true
# typed: true
require 'sorbet-runtime'
require 'sorbet-struct-comparable'
module Day20
class Pulse < T::Enum
enums do
High = new
Low = new
end
end
class Wire < T::Struct
include T::Struct::ActsAsComparable
const :src, String
const :dst, String
const :pulse, Pulse
end
class Module
extend T::Sig
extend T::Helpers
abstract!
sig { abstract.returns(String) }
def name; end
sig { abstract.returns(T::Array[String]) }
def dests; end
sig { abstract.params(source: String, pulse: Pulse).returns(T::Array[Wire]) }
def handle(source, pulse); end
end
class Broadcaster < Module
extend T::Sig
sig { override.returns(String) }
attr_reader :name
sig { override.returns(T::Array[String]) }
attr_reader :dests
sig { params(name: String, dests: T::Array[String]).void }
def initialize(name, dests)
@name = name
@dests = dests
end
sig { override.params(source: String, pulse: Pulse).returns(T::Array[Wire]) }
def handle(source, pulse)
@dests.map do |dst|
Wire::new(src: @name, dst: dst, pulse: pulse)
end
end
end
class FlipFlop < Module
extend T::Sig
sig { override.returns(String) }
attr_reader :name
sig { override.returns(T::Array[String]) }
attr_reader :dests
sig { params(name: String, dests: T::Array[String]).void }
def initialize(name, dests)
@name = name
@dests = dests
@inputs = T.let([], T::Array[String])
@on = false
end
sig { override.params(source: String, pulse: Pulse).returns(T::Array[Wire]) }
def handle(source, pulse)
return [] if pulse == Pulse::High
if @on
@on = false
output = Pulse::Low
else
@on = true
output = Pulse::High
end
@dests.map do |dst|
Wire::new(src: @name, dst: dst, pulse: output)
end
end
end
class Conjunction < Module
extend T::Sig
sig { override.returns(String) }
attr_reader :name
sig { override.returns(T::Array[String]) }
attr_reader :dests
sig { override.params(name: String, dests: T::Array[String]).void }
def initialize(name, dests)
@name = name
@dests = dests
@inputs = T.let({}, T::Hash[String, Pulse])
end
sig { params(name: String).void }
def register_input(name)
@inputs[name] = Pulse::Low
end
sig { override.params(source: String, pulse: Pulse).returns(T::Array[Wire]) }
def handle(source, pulse)
@inputs[source] = pulse
output = if @inputs.all? { |k, v| v == Pulse::High }
Pulse::Low
else
Pulse::High
end
@dests.map do |dst|
Wire::new(src: @name, dst: dst, pulse: output)
end
end
end
class Machine
extend T::Sig
def initialize(text)
@modules = T.let({}, T::Hash[String, Module])
text.strip.split("\n").each do |line|
name, dests = line.split('->').map(&:strip)
dests = dests.split(',').map(&:strip)
mod = if name[0] == '%'
FlipFlop::new(name[1..name.length], dests)
elsif name[0] == '&'
Conjunction::new(name[1..name.length], dests)
elsif name == 'broadcaster'
Broadcaster::new(name, dests)
else
raise StandardError, name
end
@modules[mod.name] = mod
end
@modules.each_pair do |src_name, src_mod|
src_mod.dests.each do |dst_name|
dst = @modules[dst_name]
if dst.is_a? Conjunction
dst.register_input(src_name)
end
end
end
end
sig { returns(T::Hash[Pulse, Integer] )}
def push_button
counts = {
Pulse::Low => 0,
Pulse::High => 0,
}
queue = T.let([], T::Array[Wire])
queue << Wire::new(src: 'button', dst: 'broadcaster', pulse: Pulse::Low)
while !queue.empty?
wire = T.must(queue.shift)
counts[wire.pulse] += 1
mod = @modules[wire.dst] or next
mod.handle(wire.src, wire.pulse).each do |output|
queue << output
end
end
counts
end
end
class << self
extend T::Sig
sig { params(path: String).returns(Integer) }
def part1(path)
text = File.read(path)
machine = Machine::new(text)
low = high = 0
1_000.times do
pulses = machine.push_button
high += pulses.fetch(Pulse::High)
low += pulses.fetch(Pulse::Low)
end
high * low
end
end
end
puts Day20::part1 File.join(__dir__, 'input/test.txt')
puts Day20::part1 File.join(__dir__, 'input/day20.txt')