Added solution for "Day 20: Pulse Propagation", part 2

This commit is contained in:
Stefan Müller 2024-08-20 17:50:07 +02:00
parent c84d3e6a2d
commit 75aab50d42
2 changed files with 66 additions and 71 deletions

View File

@ -166,6 +166,14 @@ Since the workflows are at the beginning of the puzzle input, each machine part
For part two, a virtual "multi machine part" that represents all possible values of ratings, modelled as four integer intervals, is sent through the same workflow graph. Each time one of rules is applied to a multi machine part, it is split into up to three new multi machine parts that continue to go through the workflows on separate paths. This is similar to [my day 5 solution](#day-5-if-you-give-a-seed-a-fertilizer).
### Day 20: Pulse Propagation
:mag_right: Puzzle: <https://adventofcode.com/2023/day/20>, :white_check_mark: Solver: [`UPulsePropagation.pas`](solvers/UPulsePropagation.pas)
For part 1, it's quite straight forward to model and simulate the module pulses for the first 1000 button pushes.
Part 2 seemed pretty daunting at first (and probably is quite difficult in the general case), but investigating the graph of the module connection reveals pretty quickly that the modules form a set of four independent counters of button pushes modulo different reset values, such that `rx` receives one low pulse if and only if all four counters reset as a result of the same button push. Clearly, the first time this happens is when the button is pushed a number of times equal to the product of the four counters' reset values.
### Day 22: Sand Slabs
:mag_right: Puzzle: <https://adventofcode.com/2023/day/22>, :white_check_mark: Solver: [`USandSlabs.pas`](solvers/USandSlabs.pas)

View File

@ -1,6 +1,6 @@
{
Solutions to the Advent Of Code.
Copyright (C) 2023 Stefan Müller
Copyright (C) 2023-2024 Stefan Müller
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@ -22,7 +22,7 @@ unit UPulsePropagation;
interface
uses
Classes, SysUtils, Generics.Collections, Math, USolver;
Classes, SysUtils, Generics.Collections, USolver;
type
TModule = class;
@ -49,12 +49,12 @@ type
public
property Name: string read FName;
property OutputNames: TStringList read FOutputNames;
property Outputs: TModules read FOutputs;
constructor Create(const AName: string);
destructor Destroy; override;
procedure AddInput(const AInput: TModule); virtual;
procedure AddOutput(const AOutput: TModule); virtual;
function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; virtual; abstract;
function IsOff: Boolean; virtual;
end;
{ TBroadcasterModule }
@ -71,31 +71,29 @@ type
FState: Boolean;
public
function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; override;
function IsOff: Boolean; override;
end;
{ TConjectionBuffer }
{ TConjunctionInputBuffer }
TConjectionBuffer = record
TConjunctionInputBuffer = record
Input: TModule;
LastState: Boolean;
end;
TConjectionBuffers = specialize TList<TConjectionBuffer>;
TConjunctionInputBuffers = specialize TList<TConjunctionInputBuffer>;
{ TConjunctionModule }
TConjunctionModule = class(TModule)
private
FInputBuffers: TConjectionBuffers;
FInputBuffers: TConjunctionInputBuffers;
procedure UpdateInputBuffer(constref AInput: TModule; const AState: Boolean);
function AreAllBuffersSame(const AIsHigh: Boolean): Boolean;
function AreAllBuffersHigh: Boolean;
public
constructor Create(const AName: string);
destructor Destroy; override;
procedure AddInput(const AInput: TModule); override;
function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; override;
function IsOff: Boolean; override;
end;
{ TEndpointModule }
@ -111,8 +109,6 @@ type
LowCount, HighCount: Integer;
end;
TButtonResults = specialize TList<TButtonResult>;
{ TPulsePropagation }
TPulsePropagation = class(TSolver)
@ -121,7 +117,7 @@ type
FBroadcaster: TModule;
procedure UpdateModuleConnections;
function PushButton: TButtonResult;
function AreAllModulesOff: Boolean;
function CalcCounterTarget(const AFirstFlipFlop: TModule): Int64;
public
constructor Create;
destructor Destroy; override;
@ -180,11 +176,6 @@ begin
FOutputs.Add(AOutput);
end;
function TModule.IsOff: Boolean;
begin
Result := True;
end;
{ TBroadcasterModule }
function TBroadcasterModule.ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses;
@ -204,17 +195,12 @@ begin
end;
end;
function TFlipFlopModule.IsOff: Boolean;
begin
Result := not FState;
end;
{ TConjunctionModule }
procedure TConjunctionModule.UpdateInputBuffer(constref AInput: TModule; const AState: Boolean);
var
i: Integer;
buffer: TConjectionBuffer;
buffer: TConjunctionInputBuffer;
begin
for i := 0 to FInputBuffers.Count - 1 do
if FInputBuffers[i].Input = AInput then
@ -226,13 +212,13 @@ begin
end;
end;
function TConjunctionModule.AreAllBuffersSame(const AIsHigh: Boolean): Boolean;
function TConjunctionModule.AreAllBuffersHigh: Boolean;
var
buffer: TConjectionBuffer;
buffer: TConjunctionInputBuffer;
begin
Result := True;
for buffer in FInputBuffers do
if buffer.LastState <> AIsHigh then
if not buffer.LastState then
begin
Result := False;
Exit;
@ -242,7 +228,7 @@ end;
constructor TConjunctionModule.Create(const AName: string);
begin
inherited Create(AName);
FInputBuffers := TConjectionBuffers.Create;
FInputBuffers := TConjunctionInputBuffers.Create;
end;
destructor TConjunctionModule.Destroy;
@ -253,7 +239,7 @@ end;
procedure TConjunctionModule.AddInput(const AInput: TModule);
var
buffer: TConjectionBuffer;
buffer: TConjunctionInputBuffer;
begin
buffer.Input := AInput;
buffer.LastState := False;
@ -263,12 +249,7 @@ end;
function TConjunctionModule.ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses;
begin
UpdateInputBuffer(ASender, AIsHigh);
Result := CreatePulsesToOutputs(not AreAllBuffersSame(True));
end;
function TConjunctionModule.IsOff: Boolean;
begin
Result := AreAllBuffersSame(False);
Result := CreatePulsesToOutputs(not AreAllBuffersHigh);
end;
{ TEndpointModule }
@ -342,17 +323,39 @@ begin
queue.Free;
end;
function TPulsePropagation.AreAllModulesOff: Boolean;
function TPulsePropagation.CalcCounterTarget(const AFirstFlipFlop: TModule): Int64;
var
module: TModule;
binDigit: Int64;
current, next: TModule;
i: Integer;
begin
Result := True;
for module in FModules do
if not module.IsOff then
Result := 0;
binDigit := 1;
current := AFirstFlipFlop;
while True do
begin
if current.Outputs.Count = 1 then
begin
Result := False;
Exit;
current := current.Outputs.First;
if current is TConjunctionModule then
begin
Result := Result + binDigit;
Break;
end;
end
else begin
Result := Result + binDigit;
i := 0;
repeat
if i = current.Outputs.Count then
Exit;
next := current.Outputs[i];
Inc(i);
until next is TFlipFlopModule;
current := next;
end;
binDigit := binDigit << 1;
end;
end;
constructor TPulsePropagation.Create;
@ -392,42 +395,26 @@ end;
procedure TPulsePropagation.Finish;
var
results: TButtonResults;
finalResult: TButtonResult;
cycles, remainder, i, j, max: Integer;
result, accumulated: TButtonResult;
i: Integer;
module: TModule;
begin
UpdateModuleConnections;
// The pulse counts for the full puzzle input repeat themselves in a very specific way, but the system state does not.
// This indicates there is a better solution for this problem.
// TODO: See if there is a better solution based on the repeating patterns in the pulse counts.
results := TButtonResults.Create;
repeat
results.Add(PushButton);
until AreAllModulesOff or (results.Count >= CButtonPushes);
DivMod(CButtonPushes, results.Count, cycles, remainder);
finalResult.LowCount := 0;
finalResult.HighCount := 0;
max := results.Count - 1;
for j := 0 to 1 do
accumulated.LowCount := 0;
accumulated.HighCount := 0;
for i := 1 to CButtonPushes do
begin
for i := 0 to max do
begin
Inc(finalResult.LowCount, results[i].LowCount);
Inc(finalResult.HighCount, results[i].HighCount);
end;
if j = 0 then
begin
finalResult.LowCount := finalResult.LowCount * cycles;
finalResult.HighCount := finalResult.HighCount * cycles;
max := remainder - 1;
end;
result := PushButton;
Inc(accumulated.LowCount, result.LowCount);
Inc(accumulated.HighCount, result.HighCount);
end;
results.Free;
FPart1 := accumulated.LowCount * accumulated.HighCount;
FPart1 := finalResult.LowCount * finalResult.HighCount;
FPart2 := 1;
for module in FBroadcaster.Outputs do
FPart2 := FPart2 * CalcCounterTarget(module);
end;
function TPulsePropagation.GetDataFileName: string;