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