Added solution for "Day 17: Clumsy Crucible", part 1
This commit is contained in:
parent
0ccb047312
commit
ba1cefc371
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
Solutions to the Advent Of Code.
|
||||
Copyright (C) 2023 Stefan Müller
|
||||
Copyright (C) 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,14 +22,47 @@ unit UClumsyCrucible;
|
|||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, USolver;
|
||||
Classes, SysUtils, Generics.Collections, Math, USolver;
|
||||
|
||||
type
|
||||
|
||||
{ TAxisData }
|
||||
|
||||
TAxisData = record
|
||||
// The current minimum total heat loss to get from this node to the end if the first step is on this axis.
|
||||
Minimum: Cardinal;
|
||||
// True, if and only if the minimum has been set, i.e. a path has been found from this node to the end, with the
|
||||
// first step on this axis.
|
||||
IsTraversed,
|
||||
// True. if a close node was updated (traversed) on the other axis, such that this minimum might be outdated. This
|
||||
// means that if this node is on the work list, NeedsUpdate is True for at least one of the axes.
|
||||
NeedsUpdate: Boolean;
|
||||
end;
|
||||
|
||||
{ TNode }
|
||||
|
||||
TNode = record
|
||||
Horizontal, Vertical: TAxisData;
|
||||
LocalHeatLoss: Byte;
|
||||
end;
|
||||
|
||||
TNodeArray = array of TNode;
|
||||
TNodeArrays = specialize TList<TNodeArray>;
|
||||
|
||||
TWorkQueue = specialize TQueue<TPoint>;
|
||||
|
||||
{ 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);
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
procedure ProcessDataLine(const ALine: string); override;
|
||||
procedure Finish; override;
|
||||
function GetDataFileName: string; override;
|
||||
|
@ -38,16 +71,159 @@ type
|
|||
|
||||
implementation
|
||||
|
||||
const
|
||||
CMaxStraight = 3;
|
||||
|
||||
{ TClumsyCrucible }
|
||||
|
||||
procedure TClumsyCrucible.ProcessDataLine(const ALine: string);
|
||||
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;
|
||||
end;
|
||||
|
||||
destructor TClumsyCrucible.Destroy;
|
||||
begin
|
||||
FMap.Free;
|
||||
inherited Destroy;
|
||||
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);
|
||||
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);
|
||||
end;
|
||||
|
||||
function TClumsyCrucible.GetDataFileName: string;
|
||||
|
|
|
@ -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
|
||||
|
@ -26,16 +26,6 @@ uses
|
|||
|
||||
type
|
||||
|
||||
{ TClumsyCrucibleFullDataTestCase }
|
||||
|
||||
TClumsyCrucibleFullDataTestCase = class(TEngineBaseTest)
|
||||
protected
|
||||
function CreateSolver: ISolver; override;
|
||||
published
|
||||
procedure TestPart1;
|
||||
procedure TestPart2;
|
||||
end;
|
||||
|
||||
{ TClumsyCrucibleExampleTestCase }
|
||||
|
||||
TClumsyCrucibleExampleTestCase = class(TExampleEngineBaseTest)
|
||||
|
@ -43,28 +33,10 @@ type
|
|||
function CreateSolver: ISolver; override;
|
||||
published
|
||||
procedure TestPart1;
|
||||
procedure TestPart2;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TClumsyCrucibleFullDataTestCase }
|
||||
|
||||
function TClumsyCrucibleFullDataTestCase.CreateSolver: ISolver;
|
||||
begin
|
||||
Result := TClumsyCrucible.Create;
|
||||
end;
|
||||
|
||||
procedure TClumsyCrucibleFullDataTestCase.TestPart1;
|
||||
begin
|
||||
AssertEquals(-1, FSolver.GetResultPart1);
|
||||
end;
|
||||
|
||||
procedure TClumsyCrucibleFullDataTestCase.TestPart2;
|
||||
begin
|
||||
AssertEquals(-1, FSolver.GetResultPart2);
|
||||
end;
|
||||
|
||||
{ TClumsyCrucibleExampleTestCase }
|
||||
|
||||
function TClumsyCrucibleExampleTestCase.CreateSolver: ISolver;
|
||||
|
@ -77,14 +49,8 @@ begin
|
|||
AssertEquals(102, FSolver.GetResultPart1);
|
||||
end;
|
||||
|
||||
procedure TClumsyCrucibleExampleTestCase.TestPart2;
|
||||
begin
|
||||
AssertEquals(-1, FSolver.GetResultPart2);
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
//RegisterTest(TClumsyCrucibleFullDataTestCase);
|
||||
//RegisterTest(TClumsyCrucibleExampleTestCase);
|
||||
RegisterTest('TClumsyCrucible', TClumsyCrucibleExampleTestCase);
|
||||
end.
|
||||
|
||||
|
|
Loading…
Reference in New Issue