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

This commit is contained in:
Stefan Müller 2023-12-21 16:14:00 +01:00 committed by Stefan Müller
parent b2bfbf1993
commit 55f8f3d674
6 changed files with 575 additions and 2 deletions

View File

@ -117,6 +117,10 @@
<Filename Value="UIntervals.pas"/> <Filename Value="UIntervals.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit> </Unit>
<Unit>
<Filename Value="solvers\UPulsePropagation.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
</Units> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

View File

@ -26,7 +26,7 @@ uses
Classes, SysUtils, CustApp, USolver, UTrebuchet, UCubeConundrum, UGearRatios, UScratchcards, UGiveSeedFertilizer, Classes, SysUtils, CustApp, USolver, UTrebuchet, UCubeConundrum, UGearRatios, UScratchcards, UGiveSeedFertilizer,
UWaitForIt, UCamelCards, UHauntedWasteland, UNumberTheory, UMirageMaintenance, UPipeMaze, UCosmicExpansion, UWaitForIt, UCamelCards, UHauntedWasteland, UNumberTheory, UMirageMaintenance, UPipeMaze, UCosmicExpansion,
UHotSprings, UPointOfIncidence, UParabolicReflectorDish, ULensLibrary, UFloorWillBeLava, UClumsyCrucible, UHotSprings, UPointOfIncidence, UParabolicReflectorDish, ULensLibrary, UFloorWillBeLava, UClumsyCrucible,
ULavaductLagoon, UAplenty; ULavaductLagoon, UAplenty, UPulsePropagation;
type type
@ -70,6 +70,7 @@ begin
engine.RunAndFree(TClumsyCrucible.Create); engine.RunAndFree(TClumsyCrucible.Create);
engine.RunAndFree(TLavaductLagoon.Create); engine.RunAndFree(TLavaductLagoon.Create);
engine.RunAndFree(TAplenty.Create); engine.RunAndFree(TAplenty.Create);
engine.RunAndFree(TPulsePropagation.Create);
engine.Free; engine.Free;
end; end;

View File

@ -0,0 +1,444 @@
{
Solutions to the Advent Of Code.
Copyright (C) 2023 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
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
}
unit UPulsePropagation;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, Generics.Collections, Math, USolver;
type
TModule = class;
TModules = specialize TObjectList<TModule>;
{ TPulse }
TPulse = record
Sender, Destination: TModule;
IsHigh: Boolean;
end;
TPulses = specialize TList<TPulse>;
TPulseQueue = specialize TQueue<TPulse>;
{ TModule }
TModule = class
private
FName: string;
FOutputNames: TStringList;
FOutputs: TModules;
function CreatePulsesToOutputs(const AHighPulse: Boolean): TPulses;
public
property Name: string read FName;
property OutputNames: TStringList read FOutputNames;
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 }
TBroadcasterModule = class(TModule)
public
function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; override;
end;
{ TFlipFlopModule }
TFlipFlopModule = class(TModule)
private
FState: Boolean;
public
function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; override;
function IsOff: Boolean; override;
end;
{ TConjectionBuffer }
TConjectionBuffer = record
Input: TModule;
LastState: Boolean;
end;
TConjectionBuffers = specialize TList<TConjectionBuffer>;
{ TConjunctionModule }
TConjunctionModule = class(TModule)
private
FInputBuffers: TConjectionBuffers;
procedure UpdateInputBuffer(constref AInput: TModule; const AState: Boolean);
function AreAllBuffersSame(const AIsHigh: Boolean): 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 }
TEndpointModule = class(TModule)
public
function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; override;
end;
{ TButtonResult }
TButtonResult = record
LowCount, HighCount: Integer;
end;
TButtonResults = specialize TList<TButtonResult>;
{ TPulsePropagation }
TPulsePropagation = class(TSolver)
private
FModules: TModules;
FBroadcaster: TModule;
procedure UpdateModuleConnections;
function PushButton: TButtonResult;
function AreAllModulesOff: Boolean;
public
constructor Create;
destructor Destroy; override;
procedure ProcessDataLine(const ALine: string); override;
procedure Finish; override;
function GetDataFileName: string; override;
function GetPuzzleName: string; override;
end;
const
CBroadcasterName = 'broadcaster';
CFlipFlopPrefix = '%';
CConjunctionPrefix = '&';
CButtonPushes = 1000;
implementation
{ TModule }
function TModule.CreatePulsesToOutputs(const AHighPulse: Boolean): TPulses;
var
pulse: TPulse;
output: TModule;
begin
Result := TPulses.Create;
pulse.Sender := Self;
pulse.IsHigh := AHighPulse;
for output in FOutputs do
begin
pulse.Destination := output;
Result.Add(pulse);
end;
end;
constructor TModule.Create(const AName: string);
begin
FName := AName;
FOutputNames := TStringList.Create;
FOutputs := TModules.Create(False);
end;
destructor TModule.Destroy;
begin
FOutputNames.Free;
FOutputs.Free;
inherited Destroy;
end;
procedure TModule.AddInput(const AInput: TModule);
begin
end;
procedure TModule.AddOutput(const AOutput: TModule);
begin
FOutputs.Add(AOutput);
end;
function TModule.IsOff: Boolean;
begin
Result := True;
end;
{ TBroadcasterModule }
function TBroadcasterModule.ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses;
begin
Result := CreatePulsesToOutputs(AIsHigh);
end;
{ TFlipFlopModule }
function TFlipFlopModule.ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses;
begin
if AIsHigh then
Result := TPulses.Create
else begin
FState := not FState;
Result := CreatePulsesToOutputs(FState);
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;
begin
for i := 0 to FInputBuffers.Count - 1 do
if FInputBuffers[i].Input = AInput then
begin
buffer := FInputBuffers[i];
buffer.LastState := AState;
FInputBuffers[i] := buffer;
Exit;
end;
end;
function TConjunctionModule.AreAllBuffersSame(const AIsHigh: Boolean): Boolean;
var
buffer: TConjectionBuffer;
begin
Result := True;
for buffer in FInputBuffers do
if buffer.LastState <> AIsHigh then
begin
Result := False;
Exit;
end;
end;
constructor TConjunctionModule.Create(const AName: string);
begin
inherited Create(AName);
FInputBuffers := TConjectionBuffers.Create;
end;
destructor TConjunctionModule.Destroy;
begin
FInputBuffers.Free;
inherited Destroy;
end;
procedure TConjunctionModule.AddInput(const AInput: TModule);
var
buffer: TConjectionBuffer;
begin
buffer.Input := AInput;
buffer.LastState := False;
FInputBuffers.Add(buffer);
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);
end;
{ TEndpointModule }
function TEndpointModule.ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses;
begin
Result := TPulses.Create;
end;
{ TPulsePropagation }
procedure TPulsePropagation.UpdateModuleConnections;
var
module, outModule: TModule;
name: string;
found: Boolean;
begin
for module in FModules do
begin
for name in module.OutputNames do
begin
found := False;
for outModule in FModules do
if name = outModule.Name then
begin
found := True;
Break;
end;
if not found then
begin
outModule := TEndpointModule.Create(name);
FModules.Add(outModule);
end;
module.AddOutput(outModule);
outModule.AddInput(module);
end;
module.OutputNames.Clear;
end;
end;
function TPulsePropagation.PushButton: TButtonResult;
var
queue: TPulseQueue;
pulse: TPulse;
pulses: TPulses;
begin
queue := TPulseQueue.Create;
pulse.Sender := nil;
pulse.IsHigh := False;
pulse.Destination := FBroadcaster;
queue.Enqueue(pulse);
Result.LowCount := 0;
Result.HighCount := 0;
while queue.Count > 0 do
begin
pulse := queue.Dequeue;
if pulse.IsHigh then
Inc(Result.HighCount)
else
Inc(Result.LowCount);
pulses := pulse.Destination.ReceivePulse(pulse.Sender, pulse.IsHigh);
for pulse in pulses do
queue.Enqueue(pulse);
pulses.Free;
end;
queue.Free;
end;
function TPulsePropagation.AreAllModulesOff: Boolean;
var
module: TModule;
begin
Result := True;
for module in FModules do
if not module.IsOff then
begin
Result := False;
Exit;
end;
end;
constructor TPulsePropagation.Create;
begin
FModules := TModules.Create;
end;
destructor TPulsePropagation.Destroy;
begin
FModules.Free;
inherited Destroy;
end;
procedure TPulsePropagation.ProcessDataLine(const ALine: string);
var
split: TStringArray;
module: TModule;
i: Integer;
begin
split := ALine.Split(' ');
if split[0] = CBroadcasterName then
begin
FBroadcaster := TBroadcasterModule.Create(split[0]);
module := FBroadcaster;
end
else if split[0][1] = CFlipFlopPrefix then
module := TFlipFlopModule.Create(Copy(split[0], 2, Length(split[0]) - 1))
else if split[0][1] = CConjunctionPrefix then
module := TConjunctionModule.Create(Copy(split[0], 2, Length(split[0]) - 1));
for i := 2 to Length(split) - 2 do
module.OutputNames.Add(Copy(split[i], 1, Length(split[i]) - 1));
module.OutputNames.Add(split[Length(split) - 1]);
FModules.Add(module);
end;
procedure TPulsePropagation.Finish;
var
results: TButtonResults;
finalResult: TButtonResult;
cycles, remainder, i, j, max: Integer;
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
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;
end;
results.Free;
FPart1 := finalResult.LowCount * finalResult.HighCount;
end;
function TPulsePropagation.GetDataFileName: string;
begin
Result := 'pulse_propagation.txt';
end;
function TPulsePropagation.GetPuzzleName: string;
begin
Result := 'Day 20: Pulse Propagation';
end;
end.

View File

@ -120,6 +120,10 @@
<Filename Value="UAplentyTestCases.pas"/> <Filename Value="UAplentyTestCases.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit> </Unit>
<Unit>
<Filename Value="UPulsePropagationTestCases.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
</Units> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

View File

@ -7,7 +7,8 @@ uses
UGearRatiosTestCases, UScratchcardsTestCases, UGiveSeedFertilizerTestCases, UWaitForItTestCases, UCamelCardsTestCases, UGearRatiosTestCases, UScratchcardsTestCases, UGiveSeedFertilizerTestCases, UWaitForItTestCases, UCamelCardsTestCases,
UHauntedWastelandTestCases, UMirageMaintenanceTestCases, UPipeMazeTestCases, UCosmicExpansionTestCases, UHauntedWastelandTestCases, UMirageMaintenanceTestCases, UPipeMazeTestCases, UCosmicExpansionTestCases,
UHotSpringsTestCases, UPointOfIncidenceTestCases, UParabolicReflectorDishTestCases, ULensLibraryTestCases, UHotSpringsTestCases, UPointOfIncidenceTestCases, UParabolicReflectorDishTestCases, ULensLibraryTestCases,
UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases; UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases,
UPulsePropagationTestCases;
{$R *.res} {$R *.res}

View File

@ -0,0 +1,119 @@
{
Solutions to the Advent Of Code.
Copyright (C) 2023 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
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
}
unit UPulsePropagationTestCases;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, fpcunit, testregistry, USolver, UBaseTestCases, UPulsePropagation;
type
{ TPulsePropagationFullDataTestCase }
TPulsePropagationFullDataTestCase = class(TEngineBaseTest)
protected
function CreateSolver: ISolver; override;
published
procedure TestPart1;
procedure TestPart2;
end;
{ TPulsePropagationExampleTestCase }
TPulsePropagationExampleTestCase = class(TExampleEngineBaseTest)
protected
function CreateSolver: ISolver; override;
published
procedure TestPart1;
end;
{ TExample2PulsePropagation }
TExample2PulsePropagation = class(TPulsePropagation)
function GetDataFileName: string; override;
end;
{ TPulsePropagationExample2TestCase }
TPulsePropagationExample2TestCase = class(TExampleEngineBaseTest)
protected
function CreateSolver: ISolver; override;
published
procedure TestPart1;
end;
implementation
{ TPulsePropagationFullDataTestCase }
function TPulsePropagationFullDataTestCase.CreateSolver: ISolver;
begin
Result := TPulsePropagation.Create;
end;
procedure TPulsePropagationFullDataTestCase.TestPart1;
begin
AssertEquals(949764474, FSolver.GetResultPart1);
end;
procedure TPulsePropagationFullDataTestCase.TestPart2;
begin
AssertEquals(-1, FSolver.GetResultPart2);
end;
{ TPulsePropagationExampleTestCase }
function TPulsePropagationExampleTestCase.CreateSolver: ISolver;
begin
Result := TPulsePropagation.Create;
end;
procedure TPulsePropagationExampleTestCase.TestPart1;
begin
AssertEquals(32000000, FSolver.GetResultPart1);
end;
{ TExample2PulsePropagation }
function TExample2PulsePropagation.GetDataFileName: string;
begin
Result := 'pulse_propagation2.txt';
end;
{ TPulsePropagationExample2TestCase }
function TPulsePropagationExample2TestCase.CreateSolver: ISolver;
begin
Result := TExample2PulsePropagation.Create;
end;
procedure TPulsePropagationExample2TestCase.TestPart1;
begin
AssertEquals(11687500, FSolver.GetResultPart1);
end;
initialization
RegisterTest(TPulsePropagationFullDataTestCase);
RegisterTest(TPulsePropagationExampleTestCase);
RegisterTest(TPulsePropagationExample2TestCase);
end.