Added solution for "Day 17: Clumsy Crucible", part 2
This commit is contained in:
parent
ba1cefc371
commit
7b33e8b406
|
@ -22,7 +22,7 @@ unit UClumsyCrucible;
|
|||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Generics.Collections, Math, USolver;
|
||||
Classes, SysUtils, Generics.Collections, Math, USolver, UCommon;
|
||||
|
||||
type
|
||||
|
||||
|
@ -39,27 +39,58 @@ type
|
|||
NeedsUpdate: Boolean;
|
||||
end;
|
||||
|
||||
TAxisId = (axHorizontal, axVertical);
|
||||
|
||||
const
|
||||
CAxisDirections: array[TAxisId] of array[0..1] of PPoint
|
||||
= ((@CDirectionRight, @CDirectionLeft), (@CDirectionDown, @CDirectionUp));
|
||||
COtherAxes: array[TAxisId] of TAxisId = (axVertical, axHorizontal);
|
||||
|
||||
type
|
||||
{ TNode }
|
||||
|
||||
TNode = record
|
||||
Horizontal, Vertical: TAxisData;
|
||||
Axes: array[TAxisId] of TAxisData;
|
||||
LocalHeatLoss: Byte;
|
||||
end;
|
||||
PNode = ^TNode;
|
||||
|
||||
TNodeArray = array of TNode;
|
||||
TNodeArrays = specialize TList<TNodeArray>;
|
||||
|
||||
TWorkQueue = specialize TQueue<TPoint>;
|
||||
|
||||
{ TNodeMap }
|
||||
|
||||
TNodeMap = class
|
||||
private
|
||||
// Each item in FNodes is a horizontal row of nodes.
|
||||
FNodes: TNodeArrays;
|
||||
FWidth: Integer;
|
||||
FMinStraight, FMaxStraight: Integer;
|
||||
function GetHeight: Integer;
|
||||
function GetNode(APosition: TPoint): TNode;
|
||||
function GetPNode(APosition: TPoint): PNode;
|
||||
function IsPositionInMap(constref APosition: TPoint): Boolean;
|
||||
procedure ClampPositionToMap(var APosition: TPoint);
|
||||
procedure InitWorkQueue(constref AWorkQueue: TWorkQueue);
|
||||
procedure InvalidateNeighbors(constref AWorkQueue: TWorkQueue; const AAxis: TAxisId; constref APosition: TPoint);
|
||||
function FindStepNodeMinimum(const AAxis: TAxisId; constref APosition: TPoint): Cardinal;
|
||||
public
|
||||
property Width: Integer read FWidth;
|
||||
property Height: Integer read GetHeight;
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
procedure AddNodes(const ALine: string);
|
||||
function FindMinimumPathLength(const AMinStraight, AMaxStraight: Integer): Cardinal;
|
||||
procedure Reset;
|
||||
end;
|
||||
|
||||
{ TClumsyCrucible }
|
||||
|
||||
TClumsyCrucible = class(TSolver)
|
||||
private
|
||||
// Each item in FMap is a horizontal row of nodes.
|
||||
FMap: TNodeArrays;
|
||||
FWidth: Integer;
|
||||
procedure InvalidateHorizontalNeighbors(constref APosition: TPoint; constref AWorkQueue: TWorkQueue);
|
||||
procedure InvalidateVerticalNeighbors(constref APosition: TPoint; constref AWorkQueue: TWorkQueue);
|
||||
FMap: TNodeMap;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
@ -72,39 +103,227 @@ type
|
|||
implementation
|
||||
|
||||
const
|
||||
CMinStraight = 1;
|
||||
CMaxStraight = 3;
|
||||
CUltraMinStraight = 4;
|
||||
CUltraMaxStraight = 10;
|
||||
|
||||
{ TNodeMap }
|
||||
|
||||
function TNodeMap.GetHeight: Integer;
|
||||
begin
|
||||
Result := FNodes.Count;
|
||||
end;
|
||||
|
||||
function TNodeMap.GetNode(APosition: TPoint): TNode;
|
||||
begin
|
||||
Result := FNodes[APosition.Y][APosition.X];
|
||||
end;
|
||||
|
||||
function TNodeMap.GetPNode(APosition: TPoint): PNode;
|
||||
begin
|
||||
Result := @FNodes[APosition.Y][APosition.X];
|
||||
end;
|
||||
|
||||
function TNodeMap.IsPositionInMap(constref APosition: TPoint): Boolean;
|
||||
begin
|
||||
Result := (0 <= APosition.X) and (APosition.X < Width) and (0 <= APosition.Y) and (APosition.Y < Height);
|
||||
end;
|
||||
|
||||
procedure TNodeMap.ClampPositionToMap(var APosition: TPoint);
|
||||
begin
|
||||
if APosition.X < -1 then
|
||||
APosition.X := -1
|
||||
else if APosition.X > Width then
|
||||
APosition.X := Width;
|
||||
if APosition.Y < -1 then
|
||||
APosition.Y := -1
|
||||
else if APosition.Y > Height then
|
||||
APosition.Y := Height;
|
||||
end;
|
||||
|
||||
procedure TNodeMap.InitWorkQueue(constref AWorkQueue: TWorkQueue);
|
||||
var
|
||||
position: TPoint;
|
||||
last: PNode;
|
||||
axis: TAxisId;
|
||||
begin
|
||||
// Initializes the end node and the work queue with its neighbors.
|
||||
position := Point(Width - 1, Height - 1);
|
||||
last := GetPNode(position);
|
||||
for axis in TAxisId do
|
||||
begin
|
||||
last^.Axes[axis].Minimum := 0;
|
||||
last^.Axes[axis].IsTraversed := True;
|
||||
end;
|
||||
InvalidateNeighbors(AWorkQueue, axHorizontal, position);
|
||||
InvalidateNeighbors(AWorkQueue, axVertical, position);
|
||||
end;
|
||||
|
||||
procedure TNodeMap.InvalidateNeighbors(constref AWorkQueue: TWorkQueue; const AAxis: TAxisId; constref APosition:
|
||||
TPoint);
|
||||
var
|
||||
otherAxis: TAxisId;
|
||||
nodeMinimum: Cardinal;
|
||||
direction: PPoint;
|
||||
neighborPos, stop: TPoint;
|
||||
neighbor: PNode;
|
||||
begin
|
||||
otherAxis := COtherAxes[AAxis];
|
||||
nodeMinimum := GetNode(APosition).Axes[otherAxis].Minimum;
|
||||
|
||||
for direction in CAxisDirections[AAxis] do
|
||||
begin
|
||||
neighborPos := Point(APosition.X + direction^.X * FMinStraight, APosition.Y + direction^.Y * FMinStraight);
|
||||
if IsPositionInMap(neighborPos) then
|
||||
begin
|
||||
stop := Point(APosition.X + direction^.X * (FMaxStraight + 1), APosition.Y + direction^.Y * (FMaxStraight + 1));
|
||||
ClampPositionToMap(stop);
|
||||
while neighborPos <> stop do
|
||||
begin
|
||||
neighbor := GetPNode(neighborPos);
|
||||
if not neighbor^.Axes[AAxis].NeedsUpdate
|
||||
and (not neighbor^.Axes[AAxis].IsTraversed or (neighbor^.Axes[AAxis].Minimum > nodeMinimum)) then
|
||||
begin
|
||||
neighbor^.Axes[AAxis].NeedsUpdate := True;
|
||||
if not neighbor^.Axes[otherAxis].NeedsUpdate then
|
||||
AWorkQueue.Enqueue(neighborPos);
|
||||
end;
|
||||
neighborPos := neighborPos + direction^;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TNodeMap.FindStepNodeMinimum(const AAxis: TAxisId; constref APosition: TPoint): Cardinal;
|
||||
var
|
||||
otherAxis: TAxisId;
|
||||
direction: PPoint;
|
||||
acc: Cardinal;
|
||||
neighborPos, start, stop: TPoint;
|
||||
isStartReached: Boolean;
|
||||
neighbor: TNode;
|
||||
begin
|
||||
otherAxis := COtherAxes[AAxis];
|
||||
Result := Cardinal.MaxValue;
|
||||
|
||||
for direction in CAxisDirections[AAxis] do
|
||||
begin
|
||||
acc := 0;
|
||||
isStartReached := False;
|
||||
neighborPos := APosition + direction^;
|
||||
start := Point(APosition.X + direction^.X * FMinStraight, APosition.Y + direction^.Y * FMinStraight);
|
||||
if IsPositionInMap(start) then
|
||||
begin
|
||||
stop := Point(APosition.X + direction^.X * (FMaxStraight + 1), APosition.Y + direction^.Y * (FMaxStraight + 1));
|
||||
ClampPositionToMap(stop);
|
||||
while neighborPos <> stop do
|
||||
begin
|
||||
if neighborPos = start then
|
||||
isStartReached := True;
|
||||
neighbor := GetNode(neighborPos);
|
||||
Inc(acc, neighbor.LocalHeatLoss);
|
||||
if isStartReached and neighbor.Axes[otherAxis].IsTraversed then
|
||||
Result := Min(Result, neighbor.Axes[otherAxis].Minimum + acc);
|
||||
neighborPos := neighborPos + direction^;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TNodeMap.Create;
|
||||
begin
|
||||
FNodes := TNodeArrays.Create;
|
||||
end;
|
||||
|
||||
destructor TNodeMap.Destroy;
|
||||
begin
|
||||
FNodes.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TNodeMap.AddNodes(const ALine: string);
|
||||
var
|
||||
i: Integer;
|
||||
nodes: TNodeArray;
|
||||
axis: TAxisId;
|
||||
begin
|
||||
FWidth := Length(ALine);
|
||||
SetLength(nodes, FWidth);
|
||||
for i := 0 to FWidth - 1 do
|
||||
begin
|
||||
nodes[i].LocalHeatLoss := StrToInt(ALine[i + 1]);
|
||||
for axis in TAxisId do
|
||||
begin
|
||||
nodes[i].Axes[axis].IsTraversed := False;
|
||||
nodes[i].Axes[axis].NeedsUpdate := False;
|
||||
end;
|
||||
end;
|
||||
FNodes.Add(nodes);
|
||||
end;
|
||||
|
||||
function TNodeMap.FindMinimumPathLength(const AMinStraight, AMaxStraight: Integer): Cardinal;
|
||||
var
|
||||
queue: TWorkQueue;
|
||||
position: TPoint;
|
||||
node: PNode;
|
||||
axis: TAxisId;
|
||||
start: TNode;
|
||||
newMinimum: Cardinal;
|
||||
begin
|
||||
FMinStraight := AMinStraight;
|
||||
FMaxStraight := AMaxStraight;
|
||||
|
||||
queue := TWorkQueue.Create;
|
||||
InitWorkQueue(queue);
|
||||
|
||||
// Processes work queue.
|
||||
while queue.Count > 0 do
|
||||
begin
|
||||
position := queue.Dequeue;
|
||||
node := GetPNode(position);
|
||||
|
||||
for axis in TAxisId do
|
||||
if node^.Axes[axis].NeedsUpdate then
|
||||
begin
|
||||
node^.Axes[axis].NeedsUpdate := False;
|
||||
// Finds minimum for one step from this node along this axis.
|
||||
newMinimum := FindStepNodeMinimum(axis, position);
|
||||
if not node^.Axes[axis].IsTraversed or (node^.Axes[axis].Minimum > newMinimum) then
|
||||
begin
|
||||
// Updates this axis minimum and queues update for neighbors on the other axis.
|
||||
node^.Axes[axis].IsTraversed := True;
|
||||
node^.Axes[axis].Minimum := newMinimum;
|
||||
InvalidateNeighbors(queue, COtherAxes[axis], position);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
queue.Free;
|
||||
|
||||
start := GetNode(Point(0, 0));
|
||||
Result := Min(start.Axes[axHorizontal].Minimum, start.Axes[axVertical].Minimum);
|
||||
end;
|
||||
|
||||
procedure TNodeMap.Reset;
|
||||
var
|
||||
i, j: Integer;
|
||||
axis: TAxisId;
|
||||
begin
|
||||
for i := 0 to Width - 1 do
|
||||
for j := 0 to Height - 1 do
|
||||
for axis in TAxisId do
|
||||
begin
|
||||
FNodes[j][i].Axes[axis].IsTraversed := False;
|
||||
FNodes[j][i].Axes[axis].NeedsUpdate := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TClumsyCrucible }
|
||||
|
||||
procedure TClumsyCrucible.InvalidateHorizontalNeighbors(constref APosition: TPoint; constref AWorkQueue: TWorkQueue);
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
for i := Min(FWidth - 1, APosition.X + CMaxStraight) downto Max(0, APosition.X - CMaxStraight) do
|
||||
if (i <> APosition.X) and not FMap[APosition.Y][i].Horizontal.NeedsUpdate then
|
||||
begin
|
||||
FMap[APosition.Y][i].Horizontal.NeedsUpdate := True;
|
||||
if not FMap[APosition.Y][i].Vertical.NeedsUpdate then
|
||||
AWorkQueue.Enqueue(Point(i, APosition.Y));
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TClumsyCrucible.InvalidateVerticalNeighbors(constref APosition: TPoint; constref AWorkQueue: TWorkQueue);
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
for i := Min(FMap.Count - 1, APosition.Y + CMaxStraight) downto Max(0, APosition.Y - CMaxStraight) do
|
||||
if (i <> APosition.Y) and not FMap[i][APosition.X].Vertical.NeedsUpdate then
|
||||
begin
|
||||
FMap[i][APosition.X].Vertical.NeedsUpdate := True;
|
||||
if not FMap[i][APosition.X].Horizontal.NeedsUpdate then
|
||||
AWorkQueue.Enqueue(Point(APosition.X, i));
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TClumsyCrucible.Create;
|
||||
begin
|
||||
FMap := TNodeArrays.Create;
|
||||
FMap := TNodeMap.Create;
|
||||
end;
|
||||
|
||||
destructor TClumsyCrucible.Destroy;
|
||||
|
@ -114,116 +333,15 @@ begin
|
|||
end;
|
||||
|
||||
procedure TClumsyCrucible.ProcessDataLine(const ALine: string);
|
||||
var
|
||||
i: Integer;
|
||||
nodes: TNodeArray;
|
||||
begin
|
||||
FWidth := Length(ALine);
|
||||
SetLength(nodes, FWidth);
|
||||
for i := 0 to FWidth - 1 do
|
||||
begin
|
||||
nodes[i].LocalHeatLoss := StrToInt(ALine[i + 1]);
|
||||
nodes[i].Horizontal.IsTraversed := False;
|
||||
nodes[i].Horizontal.NeedsUpdate := False;
|
||||
nodes[i].Vertical.IsTraversed := False;
|
||||
nodes[i].Vertical.NeedsUpdate := False;
|
||||
end;
|
||||
FMap.Add(nodes);
|
||||
FMap.AddNodes(ALine);
|
||||
end;
|
||||
|
||||
procedure TClumsyCrucible.Finish;
|
||||
var
|
||||
queue: TWorkQueue;
|
||||
position: TPoint;
|
||||
node: TNode;
|
||||
newMinimum, acc: Cardinal;
|
||||
i: Integer;
|
||||
begin
|
||||
queue := TWorkQueue.Create;
|
||||
|
||||
// Initializes work queue with end node.
|
||||
FMap.Last[FWidth - 1].Horizontal.Minimum := 0;
|
||||
FMap.Last[FWidth - 1].Horizontal.IsTraversed := True;
|
||||
FMap.Last[FWidth - 1].Vertical := FMap.Last[FWidth - 1].Horizontal;
|
||||
position := Point(FWidth - 1, FMap.Count - 1);
|
||||
InvalidateHorizontalNeighbors(position, queue);
|
||||
InvalidateVerticalNeighbors(position, queue);
|
||||
|
||||
// Processes work queue.
|
||||
while queue.Count > 0 do
|
||||
begin
|
||||
position := queue.Dequeue;
|
||||
node := FMap[position.Y][position.X];
|
||||
|
||||
// Updates horizontal data.
|
||||
if node.Horizontal.NeedsUpdate then
|
||||
begin
|
||||
node.Horizontal.NeedsUpdate := False;
|
||||
|
||||
// Checks for better minimum in left direction.
|
||||
newMinimum := Cardinal.MaxValue;
|
||||
acc := 0;
|
||||
for i := position.X - 1 downto Max(0, position.X - CMaxStraight) do
|
||||
begin
|
||||
Inc(acc, FMap[position.Y][i].LocalHeatLoss);
|
||||
if FMap[position.Y][i].Vertical.IsTraversed then
|
||||
newMinimum := Min(newMinimum, FMap[position.Y][i].Vertical.Minimum + acc);
|
||||
end;
|
||||
// Checks for better minimum in right direction.
|
||||
acc := 0;
|
||||
for i := position.X + 1 to Min(FWidth - 1, position.X + CMaxStraight) do
|
||||
begin
|
||||
Inc(acc, FMap[position.Y][i].LocalHeatLoss);
|
||||
if FMap[position.Y][i].Vertical.IsTraversed then
|
||||
newMinimum := Min(newMinimum, FMap[position.Y][i].Vertical.Minimum + acc);
|
||||
end;
|
||||
// Updates horizontal minimum and queues update for neighbors.
|
||||
if not node.Horizontal.IsTraversed or (node.Horizontal.Minimum > newMinimum) then
|
||||
begin
|
||||
node.Horizontal.IsTraversed := True;
|
||||
node.Horizontal.Minimum := newMinimum;
|
||||
InvalidateVerticalNeighbors(position, queue);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Updates vertical data.
|
||||
if node.Vertical.NeedsUpdate then
|
||||
begin
|
||||
node.Vertical.NeedsUpdate := False;
|
||||
|
||||
// Checks for better minimum in up direction.
|
||||
newMinimum := Cardinal.MaxValue;
|
||||
acc := 0;
|
||||
for i := position.Y - 1 downto Max(0, position.Y - CMaxStraight) do
|
||||
begin
|
||||
Inc(acc, FMap[i][position.X].LocalHeatLoss);
|
||||
if FMap[i][position.X].Horizontal.IsTraversed then
|
||||
newMinimum := Min(newMinimum, FMap[i][position.X].Horizontal.Minimum + acc);
|
||||
end;
|
||||
// Checks for better minimum in down direction.
|
||||
acc := 0;
|
||||
for i := position.Y + 1 to Min(FMap.Count - 1, position.Y + CMaxStraight) do
|
||||
begin
|
||||
Inc(acc, FMap[i][position.X].LocalHeatLoss);
|
||||
if FMap[i][position.X].Horizontal.IsTraversed then
|
||||
newMinimum := Min(newMinimum, FMap[i][position.X].Horizontal.Minimum + acc);
|
||||
end;
|
||||
// Updates vertical minimum and queues update for neighbors.
|
||||
if not node.Vertical.IsTraversed or (node.Vertical.Minimum > newMinimum) then
|
||||
begin
|
||||
node.Vertical.IsTraversed := True;
|
||||
node.Vertical.Minimum := newMinimum;
|
||||
InvalidateHorizontalNeighbors(position, queue);
|
||||
end;
|
||||
end;
|
||||
|
||||
FMap[position.Y][position.X] := node;
|
||||
end;
|
||||
|
||||
queue.Free;
|
||||
|
||||
node := FMap[0][0];
|
||||
FPart1 := Min(node.Horizontal.Minimum, node.Vertical.Minimum);
|
||||
FPart1 := FMap.FindMinimumPathLength(CMinStraight, CMaxStraight);
|
||||
FMap.Reset;
|
||||
FPart2 := FMap.FindMinimumPathLength(CUltraMinStraight, CUltraMaxStraight);
|
||||
end;
|
||||
|
||||
function TClumsyCrucible.GetDataFileName: string;
|
||||
|
|
|
@ -33,6 +33,22 @@ type
|
|||
function CreateSolver: ISolver; override;
|
||||
published
|
||||
procedure TestPart1;
|
||||
procedure TestPart2;
|
||||
end;
|
||||
|
||||
{ TExample2ClumsyCrucible }
|
||||
|
||||
TExample2ClumsyCrucible = class(TClumsyCrucible)
|
||||
function GetDataFileName: string; override;
|
||||
end;
|
||||
|
||||
{ TClumsyCrucibleExample2TestCase }
|
||||
|
||||
TClumsyCrucibleExample2TestCase = class(TExampleEngineBaseTest)
|
||||
protected
|
||||
function CreateSolver: ISolver; override;
|
||||
published
|
||||
procedure TestPart2;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
@ -49,8 +65,33 @@ begin
|
|||
AssertEquals(102, FSolver.GetResultPart1);
|
||||
end;
|
||||
|
||||
procedure TClumsyCrucibleExampleTestCase.TestPart2;
|
||||
begin
|
||||
AssertEquals(94, FSolver.GetResultPart2);
|
||||
end;
|
||||
|
||||
{ TExample2ClumsyCrucible }
|
||||
|
||||
function TExample2ClumsyCrucible.GetDataFileName: string;
|
||||
begin
|
||||
Result := 'clumsy_crucible2.txt';
|
||||
end;
|
||||
|
||||
{ TClumsyCrucibleExample2TestCase }
|
||||
|
||||
function TClumsyCrucibleExample2TestCase.CreateSolver: ISolver;
|
||||
begin
|
||||
Result := TExample2ClumsyCrucible.Create;
|
||||
end;
|
||||
|
||||
procedure TClumsyCrucibleExample2TestCase.TestPart2;
|
||||
begin
|
||||
AssertEquals(71, FSolver.GetResultPart2);
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
RegisterTest('TClumsyCrucible', TClumsyCrucibleExampleTestCase);
|
||||
RegisterTest('TClumsyCrucible', TClumsyCrucibleExample2TestCase);
|
||||
end.
|
||||
|
||||
|
|
Loading…
Reference in New Issue