Added solution for "Day 21: Step Counter", part 2
This commit is contained in:
@@ -22,7 +22,7 @@ unit UStepCounter;
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Generics.Collections, USolver, UCommon;
|
||||
Classes, SysUtils, USolver, UCommon;
|
||||
|
||||
type
|
||||
|
||||
@@ -31,13 +31,16 @@ type
|
||||
TStepCounter = class(TSolver)
|
||||
private
|
||||
FLines: TStringList;
|
||||
FWidth, FHeight, FMaxSteps: Integer;
|
||||
FWidth, FHeight, FMaxSteps1, FMaxSteps2: Integer;
|
||||
function FindStart: TPoint;
|
||||
function IsInBounds(constref APoint: TPoint): Boolean;
|
||||
function GetPosition(constref APoint: TPoint): Char;
|
||||
procedure SetPosition(constref APoint: TPoint; const AValue: Char);
|
||||
procedure PrepareMap;
|
||||
function DoSteps(const AMaxSteps: Integer): Int64;
|
||||
function CalcTargetPlotsOnInfiniteMap(const AMaxSteps: Integer): Int64;
|
||||
public
|
||||
constructor Create(const AMaxSteps: Integer = 64);
|
||||
constructor Create(const AMaxStepsPart1: Integer = 64; const AMaxStepsPart2: Integer = 26501365);
|
||||
destructor Destroy; override;
|
||||
procedure ProcessDataLine(const ALine: string); override;
|
||||
procedure Finish; override;
|
||||
@@ -48,6 +51,7 @@ type
|
||||
const
|
||||
CStartChar = 'S';
|
||||
CPlotChar = '.';
|
||||
CRockChar = '#';
|
||||
CTraversedChar = '+';
|
||||
|
||||
implementation
|
||||
@@ -87,40 +91,37 @@ begin
|
||||
FLines[APoint.Y] := s;
|
||||
end;
|
||||
|
||||
constructor TStepCounter.Create(const AMaxSteps: Integer);
|
||||
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;
|
||||
procedure TStepCounter.PrepareMap;
|
||||
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;
|
||||
plot, next: TPoint;
|
||||
pdirection: PPoint;
|
||||
begin
|
||||
FWidth := Length(FLines[0]);
|
||||
FHeight := FLines.Count;
|
||||
|
||||
currentStep := 0;
|
||||
currentPlots := TPoints.Create;
|
||||
currentPlots.Add(FindStart);
|
||||
Inc(FPart1);
|
||||
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
|
||||
for plot in currentPlots do
|
||||
for pdirection in CPCardinalDirections do
|
||||
@@ -139,15 +140,142 @@ begin
|
||||
nextPlots := temp;
|
||||
Inc(currentStep);
|
||||
|
||||
// Positions where the number of steps are even can be reached with trivial backtracking, so they count.
|
||||
if currentStep mod 2 = 0 then
|
||||
Inc(FPart1, currentPlots.Count);
|
||||
// Positions where the number of steps are even or odd (for even or odd AMaxSteps, respectively) can be reached with
|
||||
// trivial backtracking, so they count.
|
||||
if currentStep and 1 = mod2 then
|
||||
Inc(Result, currentPlots.Count);
|
||||
end;
|
||||
|
||||
currentPlots.Free;
|
||||
nextPlots.Free;
|
||||
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;
|
||||
begin
|
||||
Result := 'step_counter.txt';
|
||||
|
||||
Reference in New Issue
Block a user