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.
|
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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue