Added solution for "Day 17: Clumsy Crucible", part 1

This commit is contained in:
Stefan Müller 2024-06-19 22:38:24 +02:00
parent 0ccb047312
commit ba1cefc371
2 changed files with 181 additions and 39 deletions

View File

@ -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;

View File

@ -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.