Added solution for "Day 21: Step Counter", part 2
This commit is contained in:
parent
e7285e88b5
commit
2517c4b8cf
10
README.md
10
README.md
|
@ -174,6 +174,16 @@ For part 1, it's quite straight forward to model and simulate the module pulses
|
||||||
|
|
||||||
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.
|
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 21: Step Counter
|
||||||
|
|
||||||
|
:mag_right: Puzzle: <https://adventofcode.com/2023/day/21>, :white_check_mark: Solver: [`UStepCounter.pas`](solvers/UStepCounter.pas)
|
||||||
|
|
||||||
|
Part 1 can comfortably be solved with a flood-fill algorithm. Counting every other traversed plot will emulate the trivial backtracking the elf can do, without having to do the actual backtracking in the algorithm.
|
||||||
|
|
||||||
|
For part 2, I noticed that the map is sparse enough so that all plots that are theoretically in range are also actually in reachable. This means that the algorithm only has to count empty plots within specific, different, disjoint areas on the map, and multiply them by the number of occurences of this piece of the map within the full shape of reachable plots. See [`UStepCounter.pas`, line 174](solvers/UStepCounter.pas#L174) for details.
|
||||||
|
|
||||||
|
Interestingly, this is the only puzzle besides [day 20](#day-20-pulse-propagation), which had no part 2 example, where my implementation cannot solve the part 2 examples, since the example map is not sparse and their step limits do not fit the algorithm's requirements.
|
||||||
|
|
||||||
### 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)
|
||||||
|
|
|
@ -22,7 +22,7 @@ unit UStepCounter;
|
||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils, Generics.Collections, USolver, UCommon;
|
Classes, SysUtils, USolver, UCommon;
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
|
@ -31,13 +31,16 @@ type
|
||||||
TStepCounter = class(TSolver)
|
TStepCounter = class(TSolver)
|
||||||
private
|
private
|
||||||
FLines: TStringList;
|
FLines: TStringList;
|
||||||
FWidth, FHeight, FMaxSteps: Integer;
|
FWidth, FHeight, FMaxSteps1, FMaxSteps2: Integer;
|
||||||
function FindStart: TPoint;
|
function FindStart: TPoint;
|
||||||
function IsInBounds(constref APoint: TPoint): Boolean;
|
function IsInBounds(constref APoint: TPoint): Boolean;
|
||||||
function GetPosition(constref APoint: TPoint): Char;
|
function GetPosition(constref APoint: TPoint): Char;
|
||||||
procedure SetPosition(constref APoint: TPoint; const AValue: Char);
|
procedure SetPosition(constref APoint: TPoint; const AValue: Char);
|
||||||
|
procedure PrepareMap;
|
||||||
|
function DoSteps(const AMaxSteps: Integer): Int64;
|
||||||
|
function CalcTargetPlotsOnInfiniteMap(const AMaxSteps: Integer): Int64;
|
||||||
public
|
public
|
||||||
constructor Create(const AMaxSteps: Integer = 64);
|
constructor Create(const AMaxStepsPart1: Integer = 64; const AMaxStepsPart2: Integer = 26501365);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
procedure ProcessDataLine(const ALine: string); override;
|
procedure ProcessDataLine(const ALine: string); override;
|
||||||
procedure Finish; override;
|
procedure Finish; override;
|
||||||
|
@ -48,6 +51,7 @@ type
|
||||||
const
|
const
|
||||||
CStartChar = 'S';
|
CStartChar = 'S';
|
||||||
CPlotChar = '.';
|
CPlotChar = '.';
|
||||||
|
CRockChar = '#';
|
||||||
CTraversedChar = '+';
|
CTraversedChar = '+';
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
@ -87,40 +91,37 @@ begin
|
||||||
FLines[APoint.Y] := s;
|
FLines[APoint.Y] := s;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TStepCounter.Create(const AMaxSteps: Integer);
|
procedure TStepCounter.PrepareMap;
|
||||||
begin
|
|
||||||
FMaxSteps := AMaxSteps;
|
|
||||||
FLines := TStringList.Create;
|
|
||||||
end;
|
|
||||||
|
|
||||||
destructor TStepCounter.Destroy;
|
|
||||||
begin
|
|
||||||
FLines.Free;
|
|
||||||
inherited Destroy;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TStepCounter.ProcessDataLine(const ALine: string);
|
|
||||||
begin
|
|
||||||
FLines.Add(ALine);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TStepCounter.Finish;
|
|
||||||
var
|
var
|
||||||
currentStep: Integer;
|
i, j: Integer;
|
||||||
|
begin
|
||||||
|
for i := 2 to FWidth - 1 do
|
||||||
|
for j := 1 to FHeight - 2 do
|
||||||
|
if (FLines[j][i] <> CRockChar) and (FLines[j - 1][i] = CRockChar) and (FLines[j + 1][i] = CRockChar)
|
||||||
|
and (FLines[j][i - 1] = CRockChar) and (FLines[j][i + 1] = CRockChar) then
|
||||||
|
SetPosition(Point(i, j), CRockChar);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TStepCounter.DoSteps(const AMaxSteps: Integer): Int64;
|
||||||
|
var
|
||||||
|
mod2, currentStep: Integer;
|
||||||
currentPlots, nextPlots, temp: TPoints;
|
currentPlots, nextPlots, temp: TPoints;
|
||||||
plot, next: TPoint;
|
plot, next: TPoint;
|
||||||
pdirection: PPoint;
|
pdirection: PPoint;
|
||||||
begin
|
begin
|
||||||
FWidth := Length(FLines[0]);
|
|
||||||
FHeight := FLines.Count;
|
|
||||||
|
|
||||||
currentStep := 0;
|
currentStep := 0;
|
||||||
currentPlots := TPoints.Create;
|
currentPlots := TPoints.Create;
|
||||||
currentPlots.Add(FindStart);
|
currentPlots.Add(FindStart);
|
||||||
Inc(FPart1);
|
|
||||||
nextPlots := TPoints.Create;
|
nextPlots := TPoints.Create;
|
||||||
|
|
||||||
while currentStep < FMaxSteps do
|
// Counts the start if max steps is even.
|
||||||
|
mod2 := AMaxSteps and 1;
|
||||||
|
if mod2 = 0 then
|
||||||
|
Result := 1
|
||||||
|
else
|
||||||
|
Result := 0;
|
||||||
|
|
||||||
|
while currentStep < AMaxSteps do
|
||||||
begin
|
begin
|
||||||
for plot in currentPlots do
|
for plot in currentPlots do
|
||||||
for pdirection in CPCardinalDirections do
|
for pdirection in CPCardinalDirections do
|
||||||
|
@ -139,15 +140,142 @@ begin
|
||||||
nextPlots := temp;
|
nextPlots := temp;
|
||||||
Inc(currentStep);
|
Inc(currentStep);
|
||||||
|
|
||||||
// Positions where the number of steps are even can be reached with trivial backtracking, so they count.
|
// Positions where the number of steps are even or odd (for even or odd AMaxSteps, respectively) can be reached with
|
||||||
if currentStep mod 2 = 0 then
|
// trivial backtracking, so they count.
|
||||||
Inc(FPart1, currentPlots.Count);
|
if currentStep and 1 = mod2 then
|
||||||
|
Inc(Result, currentPlots.Count);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
currentPlots.Free;
|
currentPlots.Free;
|
||||||
nextPlots.Free;
|
nextPlots.Free;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TStepCounter.CalcTargetPlotsOnInfiniteMap(const AMaxSteps: Integer): Int64;
|
||||||
|
var
|
||||||
|
half, k, i, j: Integer;
|
||||||
|
factor1, factor1B, factor2, factor4A: Int64;
|
||||||
|
begin
|
||||||
|
Result := 0;
|
||||||
|
|
||||||
|
// Asserts square input map with odd size.
|
||||||
|
if (FWidth <> FHeight) or (FWidth and 1 = 0) then
|
||||||
|
Exit;
|
||||||
|
// Asserts half map size is odd.
|
||||||
|
half := FWidth shr 1;
|
||||||
|
if half and 1 = 0 then
|
||||||
|
Exit;
|
||||||
|
// Asserts that there is an even k such that maximum number of steps is equal to k + 1/2 times the map size.
|
||||||
|
// k is the number of visited repeated maps, not counting the start map, when taking all steps in a straight line in
|
||||||
|
// any of the four directions.
|
||||||
|
k := (AMaxSteps - half) div FWidth;
|
||||||
|
if (k and 1 = 0) and (AMaxSteps <> k * FWidth + half) then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
// Assuming that the rocks on the map are sparse enough, and the central vertical and horizontal lines are empty,
|
||||||
|
// every free plot with odd (Manhattan) distance (not larger than AMaxSteps) to the start plot (because of trivial
|
||||||
|
// backtracking) on the maps is reachable, essentially formning a 45-degree rotated square shape centered on the start
|
||||||
|
// plot.
|
||||||
|
|
||||||
|
// Inside this "diamond" shape, 2k(k - 1) + 1 (k-th centered square number) copies of the map are traversed fully.
|
||||||
|
// However, there are two different types of these. (k - 1)^2 are traversed like the start map, where all plots with
|
||||||
|
// odd distance to the center are reachable (type 1), and k^2 are traversed such that all plots within odd distance to
|
||||||
|
// the center are reachable (type 2).
|
||||||
|
|
||||||
|
// On each of the corners of this "diamond" shape, there is one map traversed fully except for two adjacent of its
|
||||||
|
// corner triangles (type 3).
|
||||||
|
|
||||||
|
// On each of the edges of this "diamond" shape, there are k maps where only the corner triangle facing towards the
|
||||||
|
// shapes center is traversed (type 4), and k - 1 maps that are fully traversed except for the corner triangle facing
|
||||||
|
// away from the shapes center (type 5).
|
||||||
|
|
||||||
|
// The four different versions of type 4 do not overlap within a map, so they can be counted together (type 4A).
|
||||||
|
|
||||||
|
// Types 1, 3, and 5 share patterns, so they can also be counted together, but the parts of the patterns have
|
||||||
|
// different counts. Each corner (type 1A) is traversed (k - 1)^2 times for type 1, 2 times for type 3, and 3(k - 1)
|
||||||
|
// for type 5, that is (k - 1)^2 + 3k - 1 in total. The center (type 1B) is traversed (k - 1)^2 times for type 1, 4
|
||||||
|
// times for type 3, and 4(k - 1) for type 5, that is (k - 1)^2 + 4k.
|
||||||
|
// Equivalently, instead type 1 is traversed (k - 1)^2 + 3k - 1 times and type 1B is traversed k + 1 times.
|
||||||
|
|
||||||
|
// Types example for k = 2, half = 5:
|
||||||
|
// 4 5 2 4A
|
||||||
|
// ........... .....O.O.O. O.O.O.O.O.O O.O.O.O.O.O
|
||||||
|
// ........... ....O.O.O.O .O.O.O.O.O. .O.O...O.O.
|
||||||
|
// ........... ...O.O.O.O. O.O.O.O.O.O O.O.....O.O
|
||||||
|
// ......#.... ..O.O.#.O.O .O.O.O#O.O. .O....#..O.
|
||||||
|
// ...#....... .O.#.O.O.O. O.O#O.O.O.O O..#......O
|
||||||
|
// ........... O.O.O.O.O.O .O.O.O.O.O. ...........
|
||||||
|
// ....#..#..O .O.O#O.#.O. O.O.#.O.#.O O...#..#..O
|
||||||
|
// .........O. O.O.O.O.O.O .O.O.O.O.O. .O.......O.
|
||||||
|
// ........O.O .O.O.O.O.O. O.O.O.O.O.O O.O.....O.O
|
||||||
|
// .......O.O. O.O.O.O.O.O .O.O.O.O.O. .O.O...O.O.
|
||||||
|
// ......O.O.O .O.O.O.O.O. O.O.O.O.O.O O.O.O.O.O.O
|
||||||
|
//
|
||||||
|
// 3 2 1 1A 1B
|
||||||
|
// .....O.O.O. O.O.O.O.O.O .O.O.O.O.O. .O.O...O.O. .....O.....
|
||||||
|
// ....O.O.O.O .O.O.O.O.O. O.O.O.O.O.O O.O.....O.O ....O.O....
|
||||||
|
// ...O.O.O.O. O.O.O.O.O.O .O.O.O.O.O. .O.......O. ...O.O.O...
|
||||||
|
// ..O.O.#.O.O .O.O.O#O.O. O.O.O.#.O.O O.....#...O ..O.O.#.O..
|
||||||
|
// .O.#.O.O.O. O.O#O.O.O.O .O.#.O.O.O. ...#....... .O.#.O.O.O.
|
||||||
|
// O.O.O.O.O.O .O.O.O.O.O. O.O.OSO.O.O ........... O.O.O.O.O.O
|
||||||
|
// .O.O#O.#.O. O.O.#.O.#.O .O.O#O.#.O. ....#..#... .O.O#O.#.O.
|
||||||
|
// ..O.O.O.O.O .O.O.O.O.O. O.O.O.O.O.O O.........O ..O.O.O.O..
|
||||||
|
// ...O.O.O.O. O.O.O.O.O.O .O.O.O.O.O. .O.......O. ...O.O.O...
|
||||||
|
// ....O.O.O.O .O.O.O.O.O. O.O.O.O.O.O O.O.....O.O ....O.O....
|
||||||
|
// .....O.O.O. O.O.O.O.O.O .O.O.O.O.O. .O.O...O.O. .....O.....
|
||||||
|
|
||||||
|
// Sets factors, aka number of occurrences, for each type.
|
||||||
|
factor1 := (k - 1) * (k - 1) + 3 * k - 1;
|
||||||
|
factor1B := k + 1;
|
||||||
|
factor2 := k * k;
|
||||||
|
factor4A := k;
|
||||||
|
for i := 0 to FWidth - 1 do
|
||||||
|
for j := 1 to FWidth do
|
||||||
|
if FLines[i][j] <> CRockChar then
|
||||||
|
if (i and 1) = (j and 1) then
|
||||||
|
begin
|
||||||
|
// Counts types 1.
|
||||||
|
Result := Result + factor1;
|
||||||
|
// Counts types 1B.
|
||||||
|
if not ((i + j <= half) or (i + j > FWidth + half) or (i - j >= half) or (j - i > half + 1)) then
|
||||||
|
Result := Result + factor1B;
|
||||||
|
end
|
||||||
|
else begin
|
||||||
|
// Counts types 2.
|
||||||
|
Result := Result + factor2;
|
||||||
|
// Counts types 4A.
|
||||||
|
if (i + j <= half) or (i + j > FWidth + half) or (i - j >= half) or (j - i > half + 1) then
|
||||||
|
Result := Result + factor4A;
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TStepCounter.Create(const AMaxStepsPart1: Integer; const AMaxStepsPart2: Integer);
|
||||||
|
begin
|
||||||
|
FMaxSteps1 := AMaxStepsPart1;
|
||||||
|
FMaxSteps2 := AMaxStepsPart2;
|
||||||
|
FLines := TStringList.Create;
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TStepCounter.Destroy;
|
||||||
|
begin
|
||||||
|
FLines.Free;
|
||||||
|
inherited Destroy;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TStepCounter.ProcessDataLine(const ALine: string);
|
||||||
|
begin
|
||||||
|
FLines.Add(ALine);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TStepCounter.Finish;
|
||||||
|
begin
|
||||||
|
FWidth := Length(FLines[0]);
|
||||||
|
FHeight := FLines.Count;
|
||||||
|
PrepareMap;
|
||||||
|
|
||||||
|
FPart2 := CalcTargetPlotsOnInfiniteMap(FMaxSteps2);
|
||||||
|
FPart1 := DoSteps(FMaxSteps1);
|
||||||
|
end;
|
||||||
|
|
||||||
function TStepCounter.GetDataFileName: string;
|
function TStepCounter.GetDataFileName: string;
|
||||||
begin
|
begin
|
||||||
Result := 'step_counter.txt';
|
Result := 'step_counter.txt';
|
||||||
|
|
|
@ -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
|
||||||
|
@ -25,10 +25,22 @@ uses
|
||||||
Classes, SysUtils, fpcunit, testregistry, USolver, UBaseTestCases, UStepCounter;
|
Classes, SysUtils, fpcunit, testregistry, USolver, UBaseTestCases, UStepCounter;
|
||||||
|
|
||||||
type
|
type
|
||||||
|
// Note that the solver implementation does not work with the examples presented
|
||||||
|
// in the puzzle description for part 2, therefore they are not represented here
|
||||||
|
// as test cases.
|
||||||
|
|
||||||
{ TStepCounterMax6ExampleTestCase }
|
{ TStepCounterExampleSteps3TestCase }
|
||||||
|
|
||||||
TStepCounterMax6ExampleTestCase = class(TExampleEngineBaseTest)
|
TStepCounterExampleSteps3TestCase = class(TExampleEngineBaseTest)
|
||||||
|
protected
|
||||||
|
function CreateSolver: ISolver; override;
|
||||||
|
published
|
||||||
|
procedure TestPart1;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TStepCounterExampleSteps6TestCase }
|
||||||
|
|
||||||
|
TStepCounterExampleSteps6TestCase = class(TExampleEngineBaseTest)
|
||||||
protected
|
protected
|
||||||
function CreateSolver: ISolver; override;
|
function CreateSolver: ISolver; override;
|
||||||
published
|
published
|
||||||
|
@ -37,20 +49,33 @@ type
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
{ TStepCounterMax6ExampleTestCase }
|
{ TStepCounterExampleSteps3TestCase }
|
||||||
|
|
||||||
function TStepCounterMax6ExampleTestCase.CreateSolver: ISolver;
|
function TStepCounterExampleSteps3TestCase.CreateSolver: ISolver;
|
||||||
begin
|
begin
|
||||||
Result := TStepCounter.Create(6);
|
Result := TStepCounter.Create(3, 3);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TStepCounterMax6ExampleTestCase.TestPart1;
|
procedure TStepCounterExampleSteps3TestCase.TestPart1;
|
||||||
|
begin
|
||||||
|
AssertEquals(6, FSolver.GetResultPart1);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TStepCounterExampleSteps6TestCase }
|
||||||
|
|
||||||
|
function TStepCounterExampleSteps6TestCase.CreateSolver: ISolver;
|
||||||
|
begin
|
||||||
|
Result := TStepCounter.Create(6, 6);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TStepCounterExampleSteps6TestCase.TestPart1;
|
||||||
begin
|
begin
|
||||||
AssertEquals(16, FSolver.GetResultPart1);
|
AssertEquals(16, FSolver.GetResultPart1);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
|
|
||||||
RegisterTest('TStepCounter', TStepCounterMax6ExampleTestCase);
|
RegisterTest('TStepCounter', TStepCounterExampleSteps3TestCase);
|
||||||
|
RegisterTest('TStepCounter', TStepCounterExampleSteps6TestCase);
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue