Updated day 12 WIP solver

This commit is contained in:
Stefan Müller 2024-10-15 11:45:44 +02:00
parent c0ee7894ae
commit 151b5dc49a
2 changed files with 421 additions and 84 deletions

View File

@ -22,7 +22,7 @@ unit UCommon;
interface
uses
Classes, SysUtils;
Classes, SysUtils, Generics.Collections;
type
PPoint = ^TPoint;
@ -39,6 +39,9 @@ const
CDirectionLeftUp: TPoint = (X: -1; Y: -1);
CPCardinalDirections: array[0..3] of PPoint = (@CDirectionRight, @CDirectionDown, @CDirectionLeft, @CDirectionUp);
type
TIntegerList = specialize TList<Integer>;
implementation
end.

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
@ -22,31 +22,97 @@ unit UHotSprings;
interface
uses
Classes, SysUtils, Generics.Collections, USolver;
Classes, SysUtils, Math, Generics.Collections, USolver, UCommon;
const
COperationalChar = '.';
CDamagedChar = '#';
CWildcardChar = '?';
COperationalPatternChars = [COperationalChar, CWildcardChar];
CDamagedPatternChars = [CDamagedChar, CWildcardChar];
CPart2Repetition = 4;
//COperationalPatternChars = [COperationalChar, CWildcardChar];
//CDamagedPatternChars = [CDamagedChar, CWildcardChar];
CPart2Repetition = 2;
type
//{ TBlockAssignment }
//
//TBlockAssignment = class
//private
// FPrevious: TBlockAssignment;
// FPattern: string;
// FValidation: TIntegerList;
//public
// property Validation: TIntegerList read FValidation;
// constructor Create(const APattern: string; constref APrevious: TBlockAssignment = nil);
// destructor Destroy; override;
// procedure WriteDebug;
//end;
//
//TBlockAssignments = specialize TObjectList<TBlockAssignment>;
TValidationLengths = array of array of Integer;
//TPatternLengths = array of Integer;
TIntegerArray = array of Integer;
{ TDamage }
TDamage = record
Start, Length, CharsRemaining: Byte;
end;
TDamages = specialize TList<TDamage>;
TDamagesBlocks = specialize TObjectList<TDamages>;
{ TConditionRecord }
TConditionRecord = class
private
FValidation: TIntegerList;
// List of non-empty, maximum-length parts of the pattern without operational springs.
FBlockPatterns: TStringList;
// Array 'a' of accumulated validation block lengths. 'a[i, j]' denotes the combined length of consecutive
// validation numbers from 'FValidation[i]' to 'FValidation[j - 1]' with a single space in between each pair of
// them.
FValidationLengths: TValidationLengths;
//FPatternLengths: TPatternLengths;
// Array 'a' of minimum indices 'a[i]', such that all remaining validation numbers starting at index 'a[i] - 1'
// cannot fit into the remaining block patterns starting at 'FBlockPatterns[i]'.
FMinIndices: TIntegerArray;
// List 'a' of lists of damages in a block pattern. Each list of damages 'a[i]' contains exactly one entry for each
// block of consecutive damages characters in the i-th block pattern.
// For example, if the pattern is '?#.??##?#?..??', then 'FDamagesBlocks' would have 3 entries, which are lists of
// 1, 2, and 0 damages, respectively.
FDamagesBlocks: TDamagesBlocks;
procedure InitValidationLengths;
//// Returns an array 'a' of accumulated pattern block lengths. 'a[i]' denotes the combined length of consecutive
//// pattern blocks starting with 'FBlockPatterns[i]' and all following with a single space in between each pair of
//// them.
//function CalcPatternLengths: TPatternLengths;
procedure InitMinIndices;
function CalcCombinations(constref AIndices: TIntegerArray): Int64;
function CalcCombinationsSingleBlock(const APattern: string; constref ADamages: TDamages; const AStartIndex,
AStopIndex: Integer): Int64;
function CalcCombinationsSingleBlockSingleValidation(const APattern: string; constref ADamages: TDamages;
const AIndex: Integer): Int64;
function ParseDamages(const APattern: string): TDamages;
public
property BlockPatterns: TStringList read FBlockPatterns;
property Validation: TIntegerList read FValidation;
constructor Create;
destructor Destroy; override;
// Adds all non-empty, maximum-length parts of the pattern without operational springs.
procedure AddBlockPatterns(const APattern: string);
function GenerateBlockAssignments: Int64;
end;
{ THotSprings }
THotSprings = class(TSolver)
private
FValidation: specialize TList<Integer>;
FSpringPattern: string;
procedure ExtendArrangement(const AArrangement: string; const ARemainingFreeOperationalCount, ACurrentValidationIndex:
Integer; var AArrangementCount: Int64);
function TryAppendOperationalChar(var AArrangement: string): Boolean;
function TryAppendValidationBlock(var AArrangement: string; const ALength: Integer): Boolean;
public
constructor Create;
destructor Destroy; override;
procedure ProcessDataLine(const ALine: string); override;
procedure Finish; override;
function GetDataFileName: string; override;
@ -55,111 +121,379 @@ type
implementation
{ THotSprings }
//{ TBlockAssignment }
//
//constructor TBlockAssignment.Create(const APattern: string; constref APrevious: TBlockAssignment);
//begin
// FPrevious := APrevious;
// FPattern := APattern;
// FValidation := TIntegerList.Create;
//end;
//
//destructor TBlockAssignment.Destroy;
//begin
// FValidation.Free;
// inherited Destroy;
//end;
//
//procedure TBlockAssignment.WriteDebug;
//var
// i: Integer;
//begin
// Write(FPattern, ' ', IntToStr(FValidation[0]));
// for i := 1 to FValidation.Count - 1 do
// Write(',', IntToStr(FValidation[i]));
// Write(' |');
//end;
procedure THotSprings.ExtendArrangement(const AArrangement: string; const ARemainingFreeOperationalCount,
ACurrentValidationIndex: Integer; var AArrangementCount: Int64);
{ TConditionRecord }
procedure TConditionRecord.InitValidationLengths;
var
match: Boolean;
temp: string;
i, j: Integer;
begin
if Length(AArrangement) = Length(FSpringPattern) then
Inc(AArrangementCount)
SetLength(FValidationLengths, FValidation.Count + 1, FValidation.Count + 1);
for i := 0 to FValidation.Count do
begin
FValidationLengths[i, i] := 0;
for j := i + 1 to FValidation.Count do
if FValidationLengths[i, j - 1] <> 0 then
FValidationLengths[i, j] := FValidationLengths[i, j - 1] + FValidation[j - 1] + 1
else
FValidationLengths[i, j] := FValidationLengths[i, j - 1] + FValidation[j - 1]
end;
end;
//function TConditionRecord.CalcPatternLengths: TPatternLengths;
//var
// i: Integer;
//begin
// SetLength(Result, FBlockPatterns.Count + 1);
// Result[FBlockPatterns.Count] := 0;
// Result[FBlockPatterns.Count - 1] := Length(FBlockPatterns[FBlockPatterns.Count - 1]);
// for i := FBlockPatterns.Count - 2 downto 0 do
// Result[i] := Result[i + 1] + 1 + Length(FBlockPatterns[i]);
//end;
procedure TConditionRecord.InitMinIndices;
var
i, j, patternsLength: Integer;
begin
SetLength(FMinIndices, FBlockPatterns.Count - 1);
patternsLength := Length(FBlockPatterns[FBlockPatterns.Count - 1]);
j := FValidation.Count;
for i := FBlockPatterns.Count - 2 downto 0 do
begin
while (j >= 0) and (FValidationLengths[j, FValidation.Count] <= patternsLength) do
Dec(j);
FMinIndices[i] := j + 1;
patternsLength := patternsLength + 1 + Length(FBlockPatterns[i]);
end;
end;
function TConditionRecord.CalcCombinations(constref AIndices: TIntegerArray): Int64;
var
i: Integer;
begin
for i in AIndices do
Write(i, ' ');
WriteLn;
Result := 1;
i := 0;
while (Result > 0) and (i < FBlockPatterns.Count) do
begin
Result := Result * CalcCombinationsSingleBlock(FBlockPatterns[i], FDamagesBlocks[i], AIndices[i], AIndices[i + 1] - 1);
Inc(i);
end;
end;
function TConditionRecord.CalcCombinationsSingleBlock(const APattern: string; constref ADamages: TDamages;
const AStartIndex, AStopIndex: Integer): Int64;
var
i, j, k: Integer;
indices: TIntegerArray;
begin
Write(' ', APattern, ' ');
for i := AStartIndex to AStopIndex do
Write(FValidation[i], ' ');
WriteLn;
// No validation number assigned to this block.
if AStartIndex > AStopIndex then
begin
if ADamages.Count = 0 then
Result := 1
else
Result := 0;
end
// One validation number assigned to this block.
else if AStartIndex = AStopIndex then
Result := CalcCombinationsSingleBlockSingleValidation(APattern, ADamages, AStartIndex)
// Multiple validation numbers assigned to this block.
else begin
temp := AArrangement;
// Tries to append a dot (operational) to the current arrangement.
if (ARemainingFreeOperationalCount > 0) and TryAppendOperationalChar(temp) then
SetLength(indices, ADamages.Count);
Write(' min before: ');
for i := AStartIndex to AStopIndex do
Write(FValidationLengths[AStartIndex, i + 1] - FValidation[i], ' ');
WriteLn;
Write(' min after: ');
for i := AStartIndex to AStopIndex do
Write(FValidationLengths[i, AStopIndex + 1] - FValidation[i], ' ');
WriteLn;
///////////////////////////////
for i := 0 to ADamages.Count - 1 do
begin
ExtendArrangement(temp, ARemainingFreeOperationalCount - 1, ACurrentValidationIndex, AArrangementCount);
WriteLn(' damage: start ',ADamages[i].Start, ', length ', ADamages[i].Length, ', remain ', ADamages[i].CharsRemaining);
Write(' ');
for j := AStartIndex to AStopIndex do
// Enough space before damage for the other validation numbers?
if (FValidationLengths[AStartIndex, j + 1] - FValidation[j] < ADamages[i].Start)
// Enough space after damage for the other validation numbers?
and (FValidationLengths[j, AStopIndex + 1] - FValidation[j] <= ADamages[i].CharsRemaining)
// Damage itself small enough for this validation number?
and (FValidation[j] >= ADamages[i].Length) then
Write(j - AStartIndex, ' ');
WriteLn;
end;
///////////////////////////////
Result := 9999;
// Assigns validation numbers to specific damages.
j := AStartIndex;
for i := 0 to ADamages.Count - 1 do
begin
while (j <= AStopIndex)
// Enough space before damage for the other validation numbers?
and ((FValidationLengths[AStartIndex, j + 1] - FValidation[j] >= ADamages[i].Start)
// Enough space after damage for the other validation numbers?
// TODO: Is this true? Once the following check is true for given j, increasing j will not make it false, so set Result := 0 and break.
or (FValidationLengths[j, AStopIndex + 1] - FValidation[j] > ADamages[i].CharsRemaining)
// Damage itself small enough for this validation number?
or (FValidation[j] < ADamages[i].Length)) do
Inc(j);
if (j > AStopIndex) then
begin
Result := 0;
Break;
end;
indices[i] := j;
end;
// Tries to append the current validation block (damaged) to the current arrangement.
if ACurrentValidationIndex < FValidation.Count then
begin
temp := AArrangement;
match := TryAppendValidationBlock(temp, FValidation[ACurrentValidationIndex]);
WriteLn(' validation number indices per damages:');
Write(' ');
for i := 0 to ADamages.Count - 1 do
Write(indices[i], ' ');
Write('( ');
for i := 0 to ADamages.Count - 1 do
Write(indices[i] - AStartIndex, ' ');
WriteLn(')');
// ... and the mandatory dot after the block, if it is not the last block.
if match
and (ACurrentValidationIndex < FValidation.Count - 1)
and not TryAppendOperationalChar(temp) then
match := False;
// TODO: Iterate over all possible assignments of validation numbers to specific damages.
end;
WriteLn(' Result: ', Result);
end;
if match then
ExtendArrangement(temp, ARemainingFreeOperationalCount, ACurrentValidationIndex + 1, AArrangementCount);
function TConditionRecord.CalcCombinationsSingleBlockSingleValidation(const APattern: string; constref ADamages:
TDamages; const AIndex: Integer): Int64;
var
combinedDamagesLength: Integer;
begin
if Length(APattern) < FValidation[AIndex] then
Result := 0
else if ADamages.Count = 0 then
Result := Length(APattern) - FValidation[AIndex] + 1
else begin
combinedDamagesLength := ADamages.Last.Start + ADamages.Last.Length - ADamages.First.Start;
if FValidation[AIndex] < combinedDamagesLength then
Result := 0
else begin
Result := Min(Min(Min(
ADamages.First.Start,
FValidation[AIndex] - combinedDamagesLength + 1),
Length(APattern) - FValidation[AIndex] + 1),
ADamages.Last.CharsRemaining + 1);
end;
end;
end;
function THotSprings.TryAppendOperationalChar(var AArrangement: string): Boolean;
begin
if FSpringPattern[Length(AArrangement) + 1] in COperationalPatternChars then
begin
AArrangement := AArrangement + COperationalChar;
Result := True;
end
else
Result := False;
end;
function THotSprings.TryAppendValidationBlock(var AArrangement: string; const ALength: Integer): Boolean;
function TConditionRecord.ParseDamages(const APattern: string): TDamages;
var
i, len: Integer;
damage: TDamage;
begin
Result := True;
len := Length(AArrangement);
for i := 1 to ALength do
begin
if FSpringPattern[len + i] in CDamagedPatternChars then
AArrangement := AArrangement + CDamagedChar
else begin
Result := False;
Break;
Result := TDamages.Create;
damage.Length := 0;
len := Length(APattern);
for i := 1 to len do
// The pattern must only contain damage and wildcard characters here.
if APattern[i] = CDamagedChar then
begin
if damage.Length = 0 then
damage.Start := i;
Inc(damage.Length);
end
else if damage.Length > 0 then
begin
damage.CharsRemaining := len - damage.Start - damage.Length + 1;
Result.Add(damage);
damage.Length := 0;
end;
if damage.Length > 0 then
begin
damage.CharsRemaining := 0;
Result.Add(damage);
end;
end;
constructor THotSprings.Create;
constructor TConditionRecord.Create;
begin
FValidation := specialize TList<Integer>.Create;
FBlockPatterns := TStringList.Create;
FValidation := TIntegerList.Create;
FDamagesBlocks := TDamagesBlocks.Create;
end;
destructor THotSprings.Destroy;
destructor TConditionRecord.Destroy;
begin
FBlockPatterns.Free;
FValidation.Free;
FDamagesBlocks.Free;
inherited Destroy;
end;
procedure THotSprings.ProcessDataLine(const ALine: string);
procedure TConditionRecord.AddBlockPatterns(const APattern: string);
var
split: TStringArray;
i, j, val, maxFreeOperationalCount: Integer;
part: string;
begin
FValidation.Clear;
split := ALine.Split([' ', ',']);
FSpringPattern := split[0];
split := APattern.Split([COperationalChar]);
for part in split do
if Length(part) > 0 then
begin
FBlockPatterns.Add(part);
FDamagesBlocks.Add(ParseDamages(part));
end;
end;
maxFreeOperationalCount := Length(FSpringPattern) - Length(split) + 2;
for i := 1 to Length(split) - 1 do
begin
val := StrToInt(split[i]);
FValidation.Add(val);
Dec(maxFreeOperationalCount, val);
end;
function TConditionRecord.GenerateBlockAssignments: Int64;
var
indices: array of Integer;
i, j, k, high: Integer;
begin
// Each loop (each call to 'CalcCombinations') represents an independent set of arrangements, defined by 'indices',
// where specific validation numbers are assigned to specific block patterns.
//
// Here, 'indices[i]' denotes the index + 1 of the last validation number assigned to 'FBlockPattern[i]', and the
// index of the first validation number in 'FValidation' assigned to 'FBlockPattern[i + 1]'. If two consecutive values
// in 'indices' are the same, then the block in between has no numbers assigned to it.
//
// Note that 'indices[0] = 0' and 'indices[FBlockPatterns.Count] = FValidation.Count' are constant. Having these two
// numbers in the array simplifies the code a bit.
InitValidationLengths;
//FPatternLengths := CalcPatternLengths;
InitMinIndices;
ExtendArrangement('', maxFreeOperationalCount, 0, FPart1);
WriteLn('Part 1: ', FPart1);
SetLength(indices, FBlockPatterns.Count + 1);
high := Length(indices) - 2;
indices[0] := 0;
indices[high + 1] := FValidation.Count;
Result := 0;
k := 0;
repeat
i := k + 1;
while i <= high do
begin
////j := indices[k];
//j := indices[i - 1];
//// TODO: FPatternLengths is only used to find the right j, so we should instead cache values to get j directly.
//while FValidationLengths[j, FValidation.Count] > FPatternLengths[i] do
// Inc(j);
//indices[i] := j;
//WriteLn(j, ' ', FMinIndices[i - 1]);
indices[i] := Max(indices[i - 1], FMinIndices[i - 1]);
while FValidationLengths[indices[i - 1], indices[i]] > Length(FBlockPatterns[i - 1]) do
begin
Dec(i);
Inc(indices[i]);
end;
Inc(i);
end;
//if FValidationLengths[indices[0], indices[1]] > Length(FBlockPatterns[0]) then
// Break;
Result := Result + CalcCombinations(indices);
k := high;
while (k > 0)
and ((indices[k] = FValidation.Count)
or (FValidationLengths[indices[k - 1], indices[k] + 1] > Length(FBlockPatterns[k - 1]))) do
Dec(k);
Inc(indices[k]);
until k = 0;
end;
{ THotSprings }
procedure THotSprings.ProcessDataLine(const ALine: string);
var
conditionRecord1, conditionRecord2: TConditionRecord;
mainSplit, split: TStringArray;
part, unfolded: string;
i: Integer;
begin
WriteLn(ALine);
WriteLn;
conditionRecord1 := TConditionRecord.Create;
conditionRecord2 := TConditionRecord.Create;
mainSplit := ALine.Split([' ']);
// Adds blocks for part 1.
conditionRecord1.AddBlockPatterns(mainSplit[0]);
// Adds blocks for part 2.
unfolded := mainSplit[0];
for i := 2 to CPart2Repetition do
unfolded := unfolded + CWildcardChar + mainSplit[0];
conditionRecord2.AddBlockPatterns(unfolded);
// Adds validation numbers.
split := mainSplit[1].Split([',']);
for part in split do
conditionRecord1.Validation.Add(StrToInt(part));
for i := 1 to CPart2Repetition do
begin
FSpringPattern := FSpringPattern + CWildcardChar + split[0];
for j := 0 to Length(split) - 2 do
FValidation.Add(FValidation[j]);
end;
maxFreeOperationalCount := (CPart2Repetition + 1) * maxFreeOperationalCount;
conditionRecord2.Validation.AddRange(conditionRecord1.Validation);
ExtendArrangement('', maxFreeOperationalCount, 0, FPart2);
WriteLn('Part 2: ', FPart2);
//for part in conditionRecord1.BlockPatterns do
// WriteLn(part);
//for i in conditionRecord1.Validation do
// WriteLn(i);
//
//WriteLn;
//
// for part in conditionRecord2.BlockPatterns do
// WriteLn(part);
// for i in conditionRecord2.Validation do
// WriteLn(i);
// WriteLn;
FPart2 := FPart2 + conditionRecord2.GenerateBlockAssignments;
conditionRecord1.Free;
conditionRecord2.Free;
WriteLn('------------------------');
WriteLn;
end;
procedure THotSprings.Finish;