From 55f8f3d674fe3122998ce0a651702bd54d77dd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 21 Dec 2023 16:14:00 +0100 Subject: [PATCH] Added solution for "Day 20: Pulse Propagation", part 1 --- AdventOfCode.lpi | 4 + AdventOfCode.lpr | 3 +- solvers/UPulsePropagation.pas | 444 +++++++++++++++++++++++++++ tests/AdventOfCodeFPCUnit.lpi | 4 + tests/AdventOfCodeFPCUnit.lpr | 3 +- tests/UPulsePropagationTestCases.pas | 119 +++++++ 6 files changed, 575 insertions(+), 2 deletions(-) create mode 100644 solvers/UPulsePropagation.pas create mode 100644 tests/UPulsePropagationTestCases.pas diff --git a/AdventOfCode.lpi b/AdventOfCode.lpi index eba98fb..14849ab 100644 --- a/AdventOfCode.lpi +++ b/AdventOfCode.lpi @@ -117,6 +117,10 @@ + + + + diff --git a/AdventOfCode.lpr b/AdventOfCode.lpr index 7d79baa..753b82f 100644 --- a/AdventOfCode.lpr +++ b/AdventOfCode.lpr @@ -26,7 +26,7 @@ uses Classes, SysUtils, CustApp, USolver, UTrebuchet, UCubeConundrum, UGearRatios, UScratchcards, UGiveSeedFertilizer, UWaitForIt, UCamelCards, UHauntedWasteland, UNumberTheory, UMirageMaintenance, UPipeMaze, UCosmicExpansion, UHotSprings, UPointOfIncidence, UParabolicReflectorDish, ULensLibrary, UFloorWillBeLava, UClumsyCrucible, - ULavaductLagoon, UAplenty; + ULavaductLagoon, UAplenty, UPulsePropagation; type @@ -70,6 +70,7 @@ begin engine.RunAndFree(TClumsyCrucible.Create); engine.RunAndFree(TLavaductLagoon.Create); engine.RunAndFree(TAplenty.Create); + engine.RunAndFree(TPulsePropagation.Create); engine.Free; end; diff --git a/solvers/UPulsePropagation.pas b/solvers/UPulsePropagation.pas new file mode 100644 index 0000000..2bfda65 --- /dev/null +++ b/solvers/UPulsePropagation.pas @@ -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 . +} + +unit UPulsePropagation; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, Generics.Collections, Math, USolver; + +type + TModule = class; + TModules = specialize TObjectList; + + { TPulse } + + TPulse = record + Sender, Destination: TModule; + IsHigh: Boolean; + end; + + TPulses = specialize TList; + TPulseQueue = specialize TQueue; + + { 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; + + { 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; + + { 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. + diff --git a/tests/AdventOfCodeFPCUnit.lpi b/tests/AdventOfCodeFPCUnit.lpi index b3c0eea..6a6220f 100644 --- a/tests/AdventOfCodeFPCUnit.lpi +++ b/tests/AdventOfCodeFPCUnit.lpi @@ -120,6 +120,10 @@ + + + + diff --git a/tests/AdventOfCodeFPCUnit.lpr b/tests/AdventOfCodeFPCUnit.lpr index 0f1a515..58d65a8 100644 --- a/tests/AdventOfCodeFPCUnit.lpr +++ b/tests/AdventOfCodeFPCUnit.lpr @@ -7,7 +7,8 @@ uses UGearRatiosTestCases, UScratchcardsTestCases, UGiveSeedFertilizerTestCases, UWaitForItTestCases, UCamelCardsTestCases, UHauntedWastelandTestCases, UMirageMaintenanceTestCases, UPipeMazeTestCases, UCosmicExpansionTestCases, UHotSpringsTestCases, UPointOfIncidenceTestCases, UParabolicReflectorDishTestCases, ULensLibraryTestCases, - UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases; + UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases, +UPulsePropagationTestCases; {$R *.res} diff --git a/tests/UPulsePropagationTestCases.pas b/tests/UPulsePropagationTestCases.pas new file mode 100644 index 0000000..c5e4503 --- /dev/null +++ b/tests/UPulsePropagationTestCases.pas @@ -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 . +} + +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. +