Robert Mion

Posted on

# Pulse Propagation

## Advent of Code 2023 Day 20

### Part 1

#### Highs and lows

Nothing exciting here. Just:

• A dictionary
• A queue
• Inputs and outputs
• Tracked state
• Conditional forwarding
• String manipulation

It probably won't be as fun as some other challenges.

But it should definitely prove...challenging.

Let's do this!

#### Parsing the input into a dictionary of modules

Given this input list of modules:

``````broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a
``````

I think I want to build a dictionary that resembles this architecture:

``````{
destinations: ['a','b','c'],
pulse: false,
type: null
},
a: {
destinations: ['b'],
power: false,
type: 'flip-flop',
},
b: {
destinations: ['c'],
power: false,
type: 'flip-flop'
},
c: {
destinations: ['inv'],
power: false,
type: 'flip-flop'
},
inv: {
destinations: ['a'],
pulses: [false],
type: 'conjunction'
},
}
``````

Writing that out made me have to re-read the rules several times.

I still don't quite understand all there is to know about pulses, remembering old pulses, and how some modules turn on and off.

But I'm sure I'll figure it out as I'm writing all the conditional logic to hopefully solve this puzzle!

Anyhoo, back to building this dictionary.

Enter: `split()`.

First, on the `->`:

``````broadcaster -> a, b, c
``````

becomes:

``````['broadcaster', 'a, b, c']
``````

Next, on the second element, only if there is a comma:

``````str.includes(',') ? str.split(', ') : str
``````

becomes:

``````['broadcaster', ['a','b','c']]
``````

And what about handling that first character?

Well, since every module except `broadcaster` has it, I can write a condition that, if passed, slices off and stores the first character:

``````let moduleType = name.slice(0,1)
name = name.slice(1)
``````

It's time to actually write this code and programmatically craft this dictionary.

...

I coded it. It's not pretty. And I'll probably have to change parts of it as I familiarize myself with the module rules.

But my code generates a dictionary of modules with destinations, initial pulse values, and types.

#### Push the button!...and troubleshoot

Let's dive in by simulating a button press and debugging my way toward the expected result.

A button press is eventually going to be a single iteration of a 1000-iteration loop.

So, I'll use a `for` loop that runs once:

``````for (let i = 0; i < 1; i++) {

}
``````

Somehow I need to make sure the program:

• Starts with `broadcaster`
• Touches every module that ever appears in a destination list
• Processes all modules in the current modules' destination list before proceeding to the next module

I'm envisioning a queue data structure and a `while` loop that runs as long as the queue isn't empty.

``````let todo = ['broadcaster']
while (todo.length > 0) {

}
``````

For now, I'll track the pulse being sent:

``````let pulse = false
``````

Processing the queue and adding items to it:

``````while (todo.length > 0) {
let next = todo.shift()
modules[next].destinations.forEach(d => {
todo.push(d)
// more important things
})
}
``````
##### First bug: not the right data type

My program kept generating an error, saying `destinations` doesn't exist.

Because sometimes I push a string to `todo`, and other times a push a 2-element array.

With a string, `modules[next]` finds a key.

With an array, `modules[next]` finds `undefined`.

One solution is to match the array data type when I craft the `broadcaster` key's value.

It's not great, but it gets rid of the error.

##### Second bug: endless loop

I assumed that queue of modules would eventually disipate.

I was wrong.

It mostly just grows.

Because every visited module has at least one destination. And each destination is being added to the list!

So, what am I missing?

Well, for staters, there's this important detail:

If a flip-flop module receives a high pulse, it is ignored and nothing happens.

That explains why the first simulated button press of the first example ends with:

``````inv -high-> a
``````

At least I now have a verifiable loop termination condition.

The question is, how will I trigger it?

Nevermind that.

The above condition shouldn't even end a loop.

It just means that the receiving module triggers no further pulses.

The real terminating rule is:

After pushing the button, you must wait until all pulses have been delivered and fully handled before pushing it again. Never push the button if modules are still processing pulses.

"...wait until all pulses have been delivered and fully handled...".

#### Rethinking how I track pulses

Pressing the button triggers a single `low` pulse with `broadcaster` as its `destination`.

So, my list of pulses is:

``````[
]
``````

I pull it from the list and apply it to `broadcaster`.

At this moment, the list is empty.

`broadcaster` has three destinations, and passes its received pulse to each of them.

So, my list of pulses after `broadcaster` is done looks like:

``````[
[false, 'a'],
[false, 'b'],
[false, 'c']
]
``````

I pull the first pulse from the list and apply it to `a`.

• `a` is a flip-flop module
• It starts as off
• It gets a low pulse
• It flips to on
• It generates a high pulse to each destination
• It only have one, `b`

After processing `a`, my pulse list looks like:

``````[
[false, 'b'],
[false, 'c'],
[true, 'b']
]
``````

The next two pulses act similar to the previous one.

Thus, the pulse list changes like this:

``````[
[false, 'c'],
[true, 'b'],
[true, 'c']
]
``````

And then:

``````[
[true, 'b'],
[true, 'c'],
[true, 'inv']
]
``````

Now `b` receives a `high` pulse.

As stated in the rules, nothing happens.

No new pulses are added to the list.

``````[
[true, 'c'],
[true, 'inv']
]
``````

Now `c` receives a `high` pulse.

As stated in the rules, nothing happens.

No new pulses are added to the list.

``````[
[true, 'inv']
]
``````

Now `inv` receives a `high` pulse.

• It's a `conjunction` module
• It defaults to remembering a `low` pulse from each input module
• It updates its memory for the inputting module, `c`

Looks like I need to also track the originating module in my pulses list?

Anyway, since `inv` remembers a `low` pulse for `c`, it sends a `high` pulse to its destinations: `a`.

The pulse list - with sending module - is now:

``````[
['inv', true, 'a']
]
``````

Now `a` receives a `high` pulse.

It does nothing.

No new pulses are generated.

And the pulse list is empty.

So this round is complete.

I think I finally understand the first example!

Better late than never!

Now how the heck am I going to re-create this programmatically??!!

#### Back to the code...and the endless struggle

Here's the current state of my one-iteration `for` loop with soon-to-be-terminating `while` loop:

``````for (let i = 0; i < 1; i++) {
let todo = [['button', false, 'broadcaster']]
let pulse = false
while (todo.length > 0) {
let [origin, bool, dest] = todo.shift()
modules[origin].destinations.forEach(d => {
switch (modules[origin].type) {
todo.push([
pulse,
d
])
break;
case 'flip-flop':
if (pulse == false) {
if (modules[origin].power) {
todo.push([
origin,
false,
dest
])
modules[origin].power = false
} else {
todo.push([
origin,
true,
dest
])
modules[origin].power = true
}
}
break;
case 'conjunction':
break;
}
})
}
}
``````

I believe I have accounted for two of the three module types.

But that third one has thrown a wrench at me.

I overlooked how complicated this detail would be:

Conjunction modules (prefix &) remember the type of the most recent pulse received from each of their connected input modules

My module-dictionary-maker only saves a module's outputs.

Not their inputs.

I need some way to store each input for each conjunction module.

I have an inkling of a strategy.

#### Generating all inputs to all conjunction modules

I'll add a `inputs` property to my conjunction modules that starts as an empty list.

I think I can do this after creating my dictionary.

``````Create a list of conjunction modules from the filtered dictionary keys
For each module, iterate through the dictionary's keys and values
If a key has that module as a destination
Add that key to the module's list of inputs
``````

Time to give it a try!

...

Thankfully that wasn't too tough.

And doing it helped me clean up my dictionary generation code.

Here's the input-cataloguing code:

``````let conjunctions = []
for (let key in modules) {
if (modules[key].type == 'conjunction') {
conjunctions.push(key)
}
}
conjunctions.forEach(c => {
for (let key in modules) {
if (modules[key].destinations.includes(c)) {
modules[c].inputs[key] = false
}
}
})
``````

After running on both example inputs, it seems to be making the correct architecture for both dictionaries.

And now....back to handling conjunction-type modules!

#### Tracking the pulses of input modules

I wrote some code that seemed to match the spec as described.

For fun, I ran one iteration.

Immediate error.

I had to re-visit my first item in `todo`:

``````['button', false, 'broadcaster']
``````

My modules dictionary has no key for `button`, nor should it!

I need to manually perform the first step of sending a low signal to each of `broadcaster`'s destination modules.

It's redundant and icky.

But it does the job.

I ran the program again for a single iteration.

It finished without errors!

I saw the same log of actions as the first example describes!

I ran for a single iteration.

Another error.

This has to do with `output` not being a key in my dictionary.

I updated my code, wrapping almost all of the logic in a condition that checks for a key's existence in the dictionary.

I ran for a single iteration again.

It finished without errors!

I saw what seemed like the same log of steps as described after a single button push.

I ran for two iterations.

Again, it all seemed to match up.

I ran for three iterations.

Uh-oh. Mismatch detected.

A conjunction module was sending the wrong pulse to its destination module.

I'm not sure why.

After some extensive logging, I noticed that my program gets things wrong after the first iteration of the second example input.

I think I'm misunderstanding the first rule about `conjunction` modules:

Conjunction modules (prefix &) remember the type of the most recent pulse received from each of their connected input modules; they initially default to remembering a low pulse for each input. When a pulse is received, the conjunction module first updates its memory for that input.

I took that to mean when a pulse is received by the input module.

But I think it means when a pulse is received by the conjunction module.

They remember the type of the most recent pulse received from each of their connected input modules.

So when a conjunction module is a destination module, it must update the pulse type in its input list for the originating module to the pulse type being passed to it.

I think...???

Honestly, I'm so confused at this point.

...

I moved and duplicated the code that updated the boolean value for any key inside a conjunction module's `input` object.

It now has the potential to happen any time a new todo is added to the list, since that is when a pulse is sent off.

I also fixed an unintended error where i was overriding a flip-flop module's power to `null`.

With all of that resolved, I can run four iterations on the second example input...

...and get what I feel is the correct result: both flip-flop modules are off.

I have very little confidence that this program will produce the correct results on my example input.

But I'm ready to move on to cataloguing and counting pulses.

#### Counting high and low pulses

Two pulse types.

Eventually, multiplied together.

I'll use a 2-element array.

It should be easy to update the array inside my `while` loop.

I also need to track when the button is pushed.

I hope that's also as easy as it seems.

...

I added the necessary increment expressions.

I ran the program on the first example a single iteration.

I saw `[8, 4]`. Woohoo!

Running 1000 times correctly generates `[8000, 4000]`. Woohoo!

Running 1000 times on the second input generates the wrong answer.

Low pulses is off by 500. High pulses is off by 1000.

Bummer.

...

Wait a second.

The sum of the pulses is 7000.

Divided by 1000 is 7.

So, 7 pulses each time?

No...there are different amounts of pulses in each four-button cycle.

...

Oh ya! The `output` module!

My program doesn't account for pulses sent to that module.

I bet that's what I'm missing!

If I look at the number of pulses in a single iteration, I count three low and three high.

There are eight total. Two go to output: one high and one low.

So my counts are correct...they just neglect that unprefixed module!

I'd wager my input doesn't contain any unprefixed modules.

Therefore, maybe my code already works on my puzzle input???

Worth a try!

Too low.

Bummer.

I'm so over this challenge.