diff --git a/README.md b/README.md index 4f4fb64..6a85f61 100644 --- a/README.md +++ b/README.md @@ -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: , :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: , :white_check_mark: Solver: [`USandSlabs.pas`](solvers/USandSlabs.pas) diff --git a/solvers/UPulsePropagation.pas b/solvers/UPulsePropagation.pas index 2bfda65..0f0f308 100644 --- a/solvers/UPulsePropagation.pas +++ b/solvers/UPulsePropagation.pas @@ -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; + TConjunctionInputBuffers = specialize TList; { 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; - { 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;