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. 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 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
@ -22,14 +22,47 @@ unit UClumsyCrucible;
interface interface
uses uses
Classes, SysUtils, USolver; Classes, SysUtils, Generics.Collections, Math, USolver;
type 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 }
TClumsyCrucible = class(TSolver) 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 public
constructor Create;
destructor Destroy; override;
procedure ProcessDataLine(const ALine: string); override; procedure ProcessDataLine(const ALine: string); override;
procedure Finish; override; procedure Finish; override;
function GetDataFileName: string; override; function GetDataFileName: string; override;
@ -38,16 +71,159 @@ type
implementation implementation
const
CMaxStraight = 3;
{ TClumsyCrucible } { TClumsyCrucible }
procedure TClumsyCrucible.ProcessDataLine(const ALine: string); procedure TClumsyCrucible.InvalidateHorizontalNeighbors(constref APosition: TPoint; constref AWorkQueue: TWorkQueue);
var
i: Integer;
begin 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; end;
procedure TClumsyCrucible.Finish; procedure TClumsyCrucible.Finish;
var
queue: TWorkQueue;
position: TPoint;
node: TNode;
newMinimum, acc: Cardinal;
i: Integer;
begin 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; end;
function TClumsyCrucible.GetDataFileName: string; function TClumsyCrucible.GetDataFileName: string;

View File

@ -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
@ -26,16 +26,6 @@ uses
type type
{ TClumsyCrucibleFullDataTestCase }
TClumsyCrucibleFullDataTestCase = class(TEngineBaseTest)
protected
function CreateSolver: ISolver; override;
published
procedure TestPart1;
procedure TestPart2;
end;
{ TClumsyCrucibleExampleTestCase } { TClumsyCrucibleExampleTestCase }
TClumsyCrucibleExampleTestCase = class(TExampleEngineBaseTest) TClumsyCrucibleExampleTestCase = class(TExampleEngineBaseTest)
@ -43,28 +33,10 @@ type
function CreateSolver: ISolver; override; function CreateSolver: ISolver; override;
published published
procedure TestPart1; procedure TestPart1;
procedure TestPart2;
end; end;
implementation 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 } { TClumsyCrucibleExampleTestCase }
function TClumsyCrucibleExampleTestCase.CreateSolver: ISolver; function TClumsyCrucibleExampleTestCase.CreateSolver: ISolver;
@ -77,14 +49,8 @@ begin
AssertEquals(102, FSolver.GetResultPart1); AssertEquals(102, FSolver.GetResultPart1);
end; end;
procedure TClumsyCrucibleExampleTestCase.TestPart2;
begin
AssertEquals(-1, FSolver.GetResultPart2);
end;
initialization initialization
//RegisterTest(TClumsyCrucibleFullDataTestCase); RegisterTest('TClumsyCrucible', TClumsyCrucibleExampleTestCase);
//RegisterTest(TClumsyCrucibleExampleTestCase);
end. end.