Merged solution for "Day 12: Hot Springs", part 2

This commit is contained in:
Stefan Müller 2024-11-19 23:54:48 +01:00
commit f61abcb9c3
9 changed files with 1170 additions and 101 deletions

View File

@ -157,6 +157,14 @@
<Filename Value="UCommon.pas"/> <Filename Value="UCommon.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit> </Unit>
<Unit>
<Filename Value="UMultiIndexEnumerator.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UBinomialCoefficients.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
</Units> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

105
UBinomialCoefficients.pas Normal file
View File

@ -0,0 +1,105 @@
{
Solutions to the Advent Of Code.
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
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
}
unit UBinomialCoefficients;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, Generics.Collections;
type
TCardinalArray = array of Cardinal;
TCardinalArrays = specialize TList<TCardinalArray>;
{ TBinomialCoefficientCache }
TBinomialCoefficientCache = class
private
FCache: TCardinalArrays;
procedure AddRow;
public
constructor Create;
destructor Destroy; override;
// Returns N choose K, with N >= K >= 0.
function Get(const AN, AK: Cardinal): Cardinal;
// Returns the number of cached rows C = N + 1, where N is the highest from previously queried "N choose K". The
// actual number of cached binomial coefficient values is C * (C + 1) / 2.
function GetCachedRowsCount: Cardinal;
end;
var
BinomialCoefficients: TBinomialCoefficientCache;
implementation
{ TBinomialCoefficientCache }
procedure TBinomialCoefficientCache.AddRow;
var
row: TCardinalArray;
i: Cardinal;
begin
SetLength(row, FCache.Count + 1);
row[0] := 1;
if FCache.Count > 0 then
begin
row[FCache.Count] := 1;
for i := 1 to FCache.Count - 1 do
row[i] := FCache.Last[i - 1] + FCache.Last[i];
end;
FCache.Add(row);
end;
constructor TBinomialCoefficientCache.Create;
begin
FCache := TCardinalArrays.Create;
end;
destructor TBinomialCoefficientCache.Destroy;
begin
FCache.Free;
inherited Destroy;
end;
function TBinomialCoefficientCache.Get(const AN, AK: Cardinal): Cardinal;
var
i: Cardinal;
begin
if AN < AK then
raise ERangeError.Create('Cannot calculate binomial coefficient "n choose k" with k larger than n.');
for i := FCache.Count to AN do
AddRow;
Result := FCache[AN][AK];
end;
function TBinomialCoefficientCache.GetCachedRowsCount: Cardinal;
begin
Result := FCache.Count;
end;
initialization
BinomialCoefficients := TBinomialCoefficientCache.Create;
finalization
BinomialCoefficients.Free;
end.

View File

@ -40,6 +40,7 @@ const
CPCardinalDirections: array[0..3] of PPoint = (@CDirectionRight, @CDirectionDown, @CDirectionLeft, @CDirectionUp); CPCardinalDirections: array[0..3] of PPoint = (@CDirectionRight, @CDirectionDown, @CDirectionLeft, @CDirectionUp);
type type
TInt64Array = array of Int64;
TIntegerList = specialize TList<Integer>; TIntegerList = specialize TList<Integer>;
TPoints = specialize TList<TPoint>; TPoints = specialize TList<TPoint>;

161
UMultiIndexEnumerator.pas Normal file
View File

@ -0,0 +1,161 @@
{
Solutions to the Advent Of Code.
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
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
}
unit UMultiIndexEnumerator;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils;
type
TIndexArray = array of Integer;
TIndexValidationResult = (ivrValid, ivrSkip, ivrBacktrack);
TEnumerableMultiIndexStrategy = class;
{ TMultiIndexEnumerator }
TMultiIndexEnumerator = class(TInterfacedObject, specialize IEnumerator<TIndexArray>)
private
FStrategy: TEnumerableMultiIndexStrategy;
FCurrent: TIndexArray;
FMustInit: Boolean;
function UpdateArray(const AInit: Boolean): Boolean;
public
constructor Create(const AStrategy: TEnumerableMultiIndexStrategy);
function GetCurrent: TIndexArray;
function MoveNext: Boolean;
procedure Reset;
property Current: TIndexArray read GetCurrent;
end;
{ TEnumerableMultiIndexStrategy }
TEnumerableMultiIndexStrategy = class(TInterfacedObject, specialize IEnumerable<TIndexArray>)
public
function GetEnumerator: specialize IEnumerator<TIndexArray>;
// Returns the number of indices to iterate over, must return positive (non-zero) value.
function GetCardinality: Integer; virtual; abstract;
function TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer;
out AStartIndexValue: Integer): Boolean; virtual; abstract;
function ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer):
TIndexValidationResult; virtual; abstract;
end;
implementation
{ TMultiIndexEnumerator }
function TMultiIndexEnumerator.UpdateArray(const AInit: Boolean): Boolean;
var
i, initialized: Integer;
r: TIndexValidationResult;
begin
if AInit then
begin
i := 0;
initialized := -1;
end
else begin
i := Length(FCurrent) - 1;
initialized := i;
end;
while i < Length(FCurrent) do
begin
if initialized < i then
begin
// Checks whether start index value can be set, and backtracks or aborts if not.
if not FStrategy.TryGetStartIndexValue(FCurrent, i, FCurrent[i]) then
if i > 0 then
begin
Dec(i);
Continue;
end
else begin
Result := False;
Exit;
end
end
else
// Sets next candidate for current index value.
Inc(FCurrent[i]);
// Checks if current index value is valid, and increases it until it is, or backtracks or aborts if so indicated.
while True do
begin
r := FStrategy.ValidateIndexValue(FCurrent, i);
case r of
ivrValid: begin
initialized := i;
Inc(i);
Break;
end;
ivrSkip:
Inc(FCurrent[i]);
ivrBacktrack:
if i > 0 then
begin
Dec(i);
Break;
end
else begin
Result := False;
Exit;
end;
end;
end;
end;
Result := True;
end;
constructor TMultiIndexEnumerator.Create(const AStrategy: TEnumerableMultiIndexStrategy);
begin
FStrategy := AStrategy;
SetLength(FCurrent, FStrategy.GetCardinality);
Reset;
end;
function TMultiIndexEnumerator.GetCurrent: TIndexArray;
begin
Result := FCurrent;
end;
function TMultiIndexEnumerator.MoveNext: Boolean;
begin
Result := UpdateArray(FMustInit);
FMustInit := False;
end;
procedure TMultiIndexEnumerator.Reset;
begin
FMustInit := True;
end;
{ TEnumerableMultiIndexStrategy }
function TEnumerableMultiIndexStrategy.GetEnumerator: specialize IEnumerator<TIndexArray>;
begin
Result := TMultiIndexEnumerator.Create(Self);
end;
end.

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
@ -22,27 +22,176 @@ unit UHotSprings;
interface interface
uses uses
Classes, SysUtils, Generics.Collections, USolver; Classes, SysUtils, Math, Generics.Collections, USolver, UCommon, UMultiIndexEnumerator, UBinomialCoefficients;
const const
COperationalChar = '.'; COperationalChar = '.';
CDamagedChar = '#'; CDamagedChar = '#';
CWildcardChar = '?'; CWildcardChar = '?';
COperationalPatternChars = [COperationalChar, CWildcardChar]; CPart2Repetition = 5;
CDamagedPatternChars = [CDamagedChar, CWildcardChar];
type type
TValidationLengths = array of array of Integer;
{ TDamage }
TDamage = record
Start, Length, CharsRemaining: Integer;
end;
TDamages = specialize TList<TDamage>;
TBlockCombinationsCache = specialize THashMap<Int64, Int64>;
TCombinationsCache = specialize TObjectHashMap<string, TBlockCombinationsCache>;
{ TBlock }
TBlock = class
private
FPattern: string;
FDamages: TDamages;
FCombinationsCache: TBlockCombinationsCache;
procedure ParseDamages;
public
constructor Create(const APattern: string; constref ACombinationsCache: TBlockCombinationsCache);
destructor Destroy; override;
property Pattern: string read FPattern;
// List of damages in this block, containing exactly one entry for each sequence of consecutive damage characters in
// the block's pattern, ordered such that a damage with lower index is further left.
// For example, if Pattern is '??##?#?', then Damages would have 2 entries.
property Damages: TDamages read FDamages;
property CombinationsCache: TBlockCombinationsCache read FCombinationsCache;
end;
TBlocks = specialize TObjectList<TBlock>;
{ TAccumulatedCombinationsMultiIndexStrategy }
// Adds accumulated combinations to the enumerable strategy to allow calculation of combinations on the fly, and
// therefore early rejection of invalid multi-index configurations.
TAccumulatedCombinationsMultiIndexStrategy = class(TEnumerableMultiIndexStrategy)
private
FAccumulatedCombinations: TInt64Array;
protected
function CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): Int64; virtual;
abstract;
function UpdateCombinations(const AValidationResult: TIndexValidationResult; constref ACurrentIndexArray:
TIndexArray; const ACurrentIndex: Integer): TIndexValidationResult;
public
function GetCombinations: Int64;
end;
TConditionRecord = class;
{ TValidationsToBlockAssignments }
// Enumerable strategy that enumerates all valid assignments of ranges of validation numbers to individual blocks in
// the form of start and stop indices.
TValidationsToBlockAssignments = class(TAccumulatedCombinationsMultiIndexStrategy)
private
FConditionRecord: TConditionRecord;
protected
function CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): Int64; override;
public
constructor Create(constref AConditionRecord: TConditionRecord);
function GetCardinality: Integer; override;
function TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer;
out AStartIndexValue: Integer): Boolean; override;
function ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer):
TIndexValidationResult; override;
end;
{ TDamageToValidationAssignments }
// Enumerable strategy that enumerates all valid assignments of each damage in the block to a specific validation
// number from the validation numbers that have been assigned to the block, as indicated by start and stop indices.
TDamageToValidationAssignments = class(TEnumerableMultiIndexStrategy)
private
FConditionRecord: TConditionRecord;
FBlock: TBlock;
FValidationStartIndex, FValidationStopIndex: Integer;
// Calculates "span", the length of all damages for one validation number combined.
function CalcValidationSpan(constref ACurrentIndexArray: TIndexArray; const ALastDamageIndex, AValidationNumber:
Integer): Integer;
public
constructor Create(constref AConditionRecord: TConditionRecord; constref ABlock: TBlock;
const AStartValidationIndex, AStopValidationIndex: Integer);
function GetCardinality: Integer; override;
function TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer;
out AStartIndexValue: Integer): Boolean; override;
function ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer):
TIndexValidationResult; override;
end;
{ TValidationPositionInfo }
TValidationPositionInfo = record
ValidationIndex, MinStart, MaxStart: Integer;
end;
TValidationPositionInfos = specialize TList<TValidationPositionInfo>;
{ TValidationPositionOffsets }
// Enumerable strategy that enumerates all valid assignments of start positions (positions mean character indices in
// the block patterns) of validation numbers that have been assigned to damages in the current block, as indicated by
// provided TValidationPositionInfos.
TValidationPositionOffsets = class(TAccumulatedCombinationsMultiIndexStrategy)
private
FConditionRecord: TConditionRecord;
FPositionInfos: TValidationPositionInfos;
FBlockLength, FValidationStartIndex, FValidationStopIndex: Integer;
protected
function CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): Int64; override;
public
constructor Create(constref AConditionRecord: TConditionRecord; constref APositionInfos: TValidationPositionInfos;
const ABlockLength, AValidationStartIndex, AValidationStopIndex: Integer);
function GetCardinality: Integer; override;
function TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer;
out AStartIndexValue: Integer): Boolean; override;
function ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer):
TIndexValidationResult; override;
end;
{ TConditionRecord }
TConditionRecord = class
private
// List of validation numbers as stated in the problem input.
FValidation: TIntegerList;
// List of non-empty, maximum-length parts of the pattern without operational springs ("blocks").
FBlocks: TBlocks;
// Array 'a' of accumulated validation series 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;
// Array 'a' of minimum indices 'a[i]', such that all remaining validation numbers starting at index 'a[i] - 1'
// cannot fit into the remaining blocks starting at 'FBlocks[i]'.
FMinIndices: TIndexArray;
FCombinationsCache: TCombinationsCache;
procedure InitValidationLengths;
procedure InitMinIndices;
function CalcCombinationsBlockSingleValidation(constref ABlock: TBlock; const AIndex: Integer): Int64;
function CalcCombinationsBlockMultiValidations(constref ABlock: TBlock; constref AIndices: TIndexArray;
const AStartIndex, AStopIndex: Integer): Int64;
function CalcValidationsId(const AStartIndex, AStopIndex: Integer): Int64;
public
constructor Create(constref ACombinationsCache: TCombinationsCache);
destructor Destroy; override;
// Adds all non-empty, maximum-length parts of the pattern without operational springs ("blocks").
procedure AddBlocks(const APattern: string);
function GenerateBlockAssignments: Int64;
function CalcCombinationsBlock(constref ABlock: TBlock; const AStartIndex, AStopIndex: Integer): Int64;
function CalcCombinationsWildcardSequence(const ASequenceLength, AStartIndex, AStopIndex: Integer): Int64;
property Validation: TIntegerList read FValidation;
property Blocks: TBlocks read FBlocks;
property ValidationLengths: TValidationLengths read FValidationLengths;
property MinIndices: TIndexArray read FMinIndices;
end;
{ THotSprings } { THotSprings }
THotSprings = class(TSolver) THotSprings = class(TSolver)
private private
FValidation: specialize TList<Integer>; FCombinationsCache: TCombinationsCache;
FSpringPattern: string;
procedure ExtendArrangement(const AArrangement: string; const ARemainingFreeOperationalCount, ACurrentValidationIndex:
Integer);
function TryAppendOperationalChar(var AArrangement: string): Boolean;
function TryAppendValidationBlock(var AArrangement: string; const ALength: Integer): Boolean;
public public
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
@ -54,99 +203,549 @@ type
implementation implementation
{ THotSprings } { TBlock }
procedure THotSprings.ExtendArrangement(const AArrangement: string; const ARemainingFreeOperationalCount, procedure TBlock.ParseDamages;
ACurrentValidationIndex: Integer);
var
match: Boolean;
temp: string;
begin
if Length(AArrangement) = Length(FSpringPattern) then
Inc(FPart1)
else begin
temp := AArrangement;
// Tries to append a dot (operational) to the current arrangement.
if (ARemainingFreeOperationalCount > 0) and TryAppendOperationalChar(temp) then
begin
ExtendArrangement(temp, ARemainingFreeOperationalCount - 1, ACurrentValidationIndex);
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]);
// ... 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;
if match then
ExtendArrangement(temp, ARemainingFreeOperationalCount, ACurrentValidationIndex + 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;
var var
i, len: Integer; i, len: Integer;
damage: TDamage;
begin
FDamages := TDamages.Create;
damage.Length := 0;
len := Length(FPattern);
for i := 1 to len do
// The pattern must only contain damage and wildcard characters here.
if FPattern[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;
FDamages.Add(damage);
damage.Length := 0;
end;
if damage.Length > 0 then
begin
damage.CharsRemaining := 0;
FDamages.Add(damage);
end;
end;
constructor TBlock.Create(const APattern: string; constref ACombinationsCache: TBlockCombinationsCache);
begin
FPattern := APattern;
FCombinationsCache := ACombinationsCache;
ParseDamages;
end;
destructor TBlock.Destroy;
begin
FDamages.Free;
inherited Destroy;
end;
{ TAccumulatedCombinationsMultiIndexStrategy }
function TAccumulatedCombinationsMultiIndexStrategy.UpdateCombinations(const AValidationResult: TIndexValidationResult;
constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): TIndexValidationResult;
var
combinations: Int64;
begin
Result := AValidationResult;
if Result = ivrValid then
begin
combinations := CalcCombinations(ACurrentIndexArray, ACurrentIndex);
if combinations = 0 then
Result := ivrSkip
else if ACurrentIndex > 0 then
FAccumulatedCombinations[ACurrentIndex] := combinations * FAccumulatedCombinations[ACurrentIndex - 1]
else begin
SetLength(FAccumulatedCombinations, GetCardinality);
FAccumulatedCombinations[ACurrentIndex] := combinations;
end;
end;
end;
function TAccumulatedCombinationsMultiIndexStrategy.GetCombinations: Int64;
begin
if FAccumulatedCombinations <> nil then
Result := FAccumulatedCombinations[GetCardinality - 1]
else
Result := 0;
end;
{ TValidationsToBlockAssignments }
function TValidationsToBlockAssignments.CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex:
Integer): Int64;
var
block: TBlock;
start, stop: Integer;
begin
// 'ACurrentIndexArray[i] - 1' denotes the index of the last validation number assigned to 'Block[i]', and the index
// of the first validation number in 'Validation' assigned to 'Block[i + 1]'. If two consecutive values in
// 'ACurrentIndexArray' are the same, then the block in between has no numbers assigned to it.
block := FConditionRecord.Blocks[ACurrentIndex];
if ACurrentIndex > 0 then
start := ACurrentIndexArray[ACurrentIndex - 1]
else
start := 0;
stop := ACurrentIndexArray[ACurrentIndex] - 1;
if block.Damages.Count > 0 then
Result := FConditionRecord.CalcCombinationsBlock(block, start, stop)
else
Result := FConditionRecord.CalcCombinationsWildcardSequence(Length(block.Pattern), start, stop);
end;
constructor TValidationsToBlockAssignments.Create(constref AConditionRecord: TConditionRecord);
begin
FConditionRecord := AConditionRecord;
end;
function TValidationsToBlockAssignments.GetCardinality: Integer;
begin
Result := FConditionRecord.Blocks.Count;
end;
function TValidationsToBlockAssignments.TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray;
const ACurrentIndex: Integer; out AStartIndexValue: Integer): Boolean;
begin begin
Result := True; Result := True;
len := Length(AArrangement); if ACurrentIndex + 1 = GetCardinality then
for i := 1 to ALength do AStartIndexValue := FConditionRecord.Validation.Count
else if ACurrentIndex > 0 then
AStartIndexValue := Max(ACurrentIndexArray[ACurrentIndex - 1], FConditionRecord.MinIndices[ACurrentIndex])
else
AStartIndexValue := FConditionRecord.MinIndices[ACurrentIndex];
end;
function TValidationsToBlockAssignments.ValidateIndexValue(constref ACurrentIndexArray: TIndexArray;
const ACurrentIndex: Integer): TIndexValidationResult;
var
start: Integer;
begin begin
if FSpringPattern[len + i] in CDamagedPatternChars then if ACurrentIndexArray[ACurrentIndex] > FConditionRecord.Validation.Count then
AArrangement := AArrangement + CDamagedChar Result := ivrBacktrack
else begin else begin
Result := False; if ACurrentIndex > 0 then
Break; start := ACurrentIndexArray[ACurrentIndex - 1]
else
start := 0;
if FConditionRecord.ValidationLengths[start, ACurrentIndexArray[ACurrentIndex]]
<= Length(FConditionRecord.Blocks[ACurrentIndex].Pattern) then
Result := ivrValid
else
Result := ivrBacktrack;
end;
Result := UpdateCombinations(Result, ACurrentIndexArray, ACurrentIndex);
end;
{ TDamageToValidationAssignments }
function TDamageToValidationAssignments.CalcValidationSpan(constref ACurrentIndexArray: TIndexArray;
const ALastDamageIndex, AValidationNumber: Integer): Integer;
var
spanStart: Integer;
begin
spanStart := ALastDamageIndex;
while (spanStart > 0) and (ACurrentIndexArray[spanStart - 1] = AValidationNumber) do
Dec(spanStart);
Result := FBlock.Damages[ALastDamageIndex].Length;
if spanStart < ALastDamageIndex then
Inc(Result, FBlock.Damages[ALastDamageIndex].Start - FBlock.Damages[spanStart].Start);
end;
constructor TDamageToValidationAssignments.Create(constref AConditionRecord: TConditionRecord; constref ABlock: TBlock;
const AStartValidationIndex, AStopValidationIndex: Integer);
begin
FConditionRecord := AConditionRecord;
FBlock := ABlock;
FValidationStartIndex := AStartValidationIndex;
FValidationStopIndex := AStopValidationIndex;
end;
function TDamageToValidationAssignments.GetCardinality: Integer;
begin
Result := FBlock.Damages.Count;
end;
function TDamageToValidationAssignments.TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray;
const ACurrentIndex: Integer; out AStartIndexValue: Integer): Boolean;
begin
Result := True;
if ACurrentIndex > 0 then
AStartIndexValue := ACurrentIndexArray[ACurrentIndex - 1]
else
AStartIndexValue := FValidationStartIndex;
end;
function TDamageToValidationAssignments.ValidateIndexValue(constref ACurrentIndexArray: TIndexArray;
const ACurrentIndex: Integer): TIndexValidationResult;
var
i, prev: Integer;
begin
i := ACurrentIndexArray[ACurrentIndex];
prev := ACurrentIndex - 1;
// Checks maximum index value.
if i > FValidationStopIndex then
Result := ivrBacktrack
// Checks if there is enough space after this damage for remaining validation numbers.
else if (i < FValidationStopIndex)
and (FConditionRecord.ValidationLengths[i + 1, FValidationStopIndex + 1] + 1 > FBlock.Damages[ACurrentIndex].CharsRemaining) then
Result := ivrSkip
// Checks if there is enough space before this damage for previous validation numbers.
else if (FValidationStartIndex < i)
and (FConditionRecord.ValidationLengths[FValidationStartIndex, i] + 1 >= FBlock.Damages[ACurrentIndex].Start) then
Result := ivrBacktrack
// Checks if there is enough space between previous and this damage for skipped validation numbers.
else if (ACurrentIndex > 0)
and (ACurrentIndexArray[prev] + 1 < i)
and (FConditionRecord.ValidationLengths[ACurrentIndexArray[prev] + 1, i] + 2
> FBlock.Damages[ACurrentIndex].Start - FBlock.Damages[prev].Start - FBlock.Damages[prev].Length) then
Result := ivrBacktrack
// Checks if span is small enough to fit within this validation number.
else if FConditionRecord.Validation[i] < CalcValidationSpan(ACurrentIndexArray, ACurrentIndex, i) then
Result := ivrSkip
else
Result := ivrValid;
end;
{ TValidationPositionOffsets }
function TValidationPositionOffsets.CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex:
Integer): Int64;
var
space, start, stop: Integer;
begin
stop := FPositionInfos[ACurrentIndex].ValidationIndex - 1;
if ACurrentIndex > 0 then
begin
space := ACurrentIndexArray[ACurrentIndex] - ACurrentIndexArray[ACurrentIndex - 1]
- FConditionRecord.Validation[FPositionInfos[ACurrentIndex - 1].ValidationIndex] - 2;
start := FPositionInfos[ACurrentIndex - 1].ValidationIndex + 1;
Result := FConditionRecord.CalcCombinationsWildcardSequence(space, start, stop);
end
else begin
// Handles first calculated offset.
space := ACurrentIndexArray[0] - 2;
Result := FConditionRecord.CalcCombinationsWildcardSequence(space, FValidationStartIndex, stop);
end;
if (Result > 0) and (ACurrentIndex + 1 = GetCardinality) then
begin
// Handles last calculated offset.
space := FBlockLength - ACurrentIndexArray[ACurrentIndex] - FConditionRecord.Validation[FPositionInfos.Last.ValidationIndex];
start := FPositionInfos.Last.ValidationIndex + 1;
Result := Result * FConditionRecord.CalcCombinationsWildcardSequence(space, start, FValidationStopIndex);
end;
end;
constructor TValidationPositionOffsets.Create(constref AConditionRecord: TConditionRecord; constref APositionInfos:
TValidationPositionInfos; const ABlockLength, AValidationStartIndex, AValidationStopIndex: Integer);
begin
FConditionRecord := AConditionRecord;
FPositionInfos := APositionInfos;
FBlockLength := ABlockLength;
FValidationStartIndex := AValidationStartIndex;
FValidationStopIndex := AValidationStopIndex;
inherited Create;
end;
function TValidationPositionOffsets.GetCardinality: Integer;
begin
Result := FPositionInfos.Count;
end;
function TValidationPositionOffsets.TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray;
const ACurrentIndex: Integer; out AStartIndexValue: Integer): Boolean;
var
info: TValidationPositionInfo;
begin
info := FPositionInfos[ACurrentIndex];
AStartIndexValue := info.MinStart;
// Adjusts start value to avoid overlap of this validation number with the previous one (the one from previous
// position info).
if ACurrentIndex > 0 then
AStartIndexValue := Max(AStartIndexValue,
ACurrentIndexArray[ACurrentIndex - 1] + FConditionRecord.Validation[FPositionInfos[ACurrentIndex - 1].ValidationIndex] + 1);
Result := True;
end;
function TValidationPositionOffsets.ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex:
Integer): TIndexValidationResult;
begin
if ACurrentIndexArray[ACurrentIndex] <= FPositionInfos[ACurrentIndex].MaxStart then
Result := ivrValid
else
Result := ivrBacktrack;
Result := UpdateCombinations(Result, ACurrentIndexArray, ACurrentIndex);
end;
{ TConditionRecord }
procedure TConditionRecord.InitValidationLengths;
var
i, j: Integer;
begin
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;
procedure TConditionRecord.InitMinIndices;
var
i, j, patternsLength: Integer;
begin
SetLength(FMinIndices, FBlocks.Count - 1);
patternsLength := Length(FBlocks[FBlocks.Count - 1].Pattern);
j := FValidation.Count;
for i := FBlocks.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(FBlocks[i].Pattern);
end;
end;
function TConditionRecord.CalcCombinationsBlockSingleValidation(constref ABlock: TBlock; const AIndex: Integer): Int64;
var
len, combinedDamagesLength: Integer;
begin
len := Length(ABlock.Pattern);
if len < FValidation[AIndex] then
Result := 0
else if ABlock.Damages.Count = 0 then
Result := len - FValidation[AIndex] + 1
else begin
combinedDamagesLength := ABlock.Damages.Last.Start + ABlock.Damages.Last.Length - ABlock.Damages.First.Start;
if FValidation[AIndex] < combinedDamagesLength then
Result := 0
else begin
Result := Min(Min(Min(
ABlock.Damages.First.Start,
FValidation[AIndex] - combinedDamagesLength + 1),
len - FValidation[AIndex] + 1),
ABlock.Damages.Last.CharsRemaining + 1);
end; end;
end; end;
end; end;
function TConditionRecord.CalcCombinationsBlockMultiValidations(constref ABlock: TBlock; constref AIndices:
TIndexArray; const AStartIndex, AStopIndex: Integer): Int64;
var
i, high: Integer;
position: TValidationPositionInfo;
positions: TValidationPositionInfos;
validationPositionOffsets: TValidationPositionOffsets;
offsets: TIndexArray;
begin
positions := TValidationPositionInfos.Create;
high := Length(AIndices) - 1;
// Initializes first info record.
position.ValidationIndex := AIndices[0];
position.MaxStart := ABlock.Damages[0].Start;
position.MinStart := 1;
for i := 1 to high do
if AIndices[i] <> position.ValidationIndex then
begin
// Finalizes current info record.
position.MaxStart := Min(position.MaxStart, ABlock.Damages[i].Start - 1 - FValidation[position.ValidationIndex]);
position.MinStart := Max(position.MinStart,
ABlock.Damages[i - 1].Start + ABlock.Damages[i - 1].Length - FValidation[position.ValidationIndex]);
positions.Add(position);
// Initializes next info record.
position.ValidationIndex := AIndices[i];
position.MaxStart := ABlock.Damages[i].Start;
position.MinStart := position.MinStart + FValidationLengths[AIndices[i - 1], AIndices[i]] + 1;
end;
// Finalizes last info record.
position.MaxStart := Min(position.MaxStart, Length(ABlock.Pattern) + 1 - FValidation[position.ValidationIndex]);
position.MinStart := Max(position.MinStart,
ABlock.Damages[high].Start + ABlock.Damages[high].Length - FValidation[position.ValidationIndex]);
positions.Add(position);
Result := 0;
validationPositionOffsets := TValidationPositionOffsets.Create(Self, positions, Length(ABlock.Pattern),
AStartIndex, AStopIndex);
for offsets in validationPositionOffsets do
Result := Result + validationPositionOffsets.GetCombinations;
validationPositionOffsets.Free;
positions.Free;
end;
function TConditionRecord.CalcValidationsId(const AStartIndex, AStopIndex: Integer): Int64;
var
i: Integer;
begin
// Requires 'FValidations[i] < 32' for each 'i' and 'AStopIndex - AStartIndex < 12'.
Result := FValidation[AStartIndex];
for i := AStartIndex + 1 to AStopIndex do
Result := (Result shl 5) or FValidation[i];
end;
constructor TConditionRecord.Create(constref ACombinationsCache: TCombinationsCache);
begin
FBlocks := TBlocks.Create;
FValidation := TIntegerList.Create;
FCombinationsCache := ACombinationsCache;
end;
destructor TConditionRecord.Destroy;
begin
FBlocks.Free;
FValidation.Free;
inherited Destroy;
end;
procedure TConditionRecord.AddBlocks(const APattern: string);
var
split: TStringArray;
part: string;
blockCache: TBlockCombinationsCache;
begin
split := APattern.Split([COperationalChar]);
for part in split do
if Length(part) > 0 then
begin
if not FCombinationsCache.TryGetValue(part, blockCache) then
begin
blockCache := TBlockCombinationsCache.Create;
FCombinationsCache.Add(part, blockCache);
end;
FBlocks.Add(TBlock.Create(part, blockCache));
end;
end;
function TConditionRecord.GenerateBlockAssignments: Int64;
var
validationsToBlockAssignments: TValidationsToBlockAssignments;
indices: TIndexArray;
begin
InitValidationLengths;
InitMinIndices;
Result := 0;
validationsToBlockAssignments := TValidationsToBlockAssignments.Create(Self);
for indices in validationsToBlockAssignments do
Result := Result + validationsToBlockAssignments.GetCombinations;
validationsToBlockAssignments.Free;
end;
function TConditionRecord.CalcCombinationsBlock(constref ABlock: TBlock; const AStartIndex, AStopIndex: Integer): Int64;
var
validationsId: Int64;
indices: TIndexArray;
damageToValidationAssignments: TDamageToValidationAssignments;
begin
// No validation number assigned to this block.
if AStartIndex > AStopIndex then
begin
if ABlock.Damages.Count = 0 then
Result := 1
else
Result := 0;
end
// One validation number assigned to this block.
else if AStartIndex = AStopIndex then
Result := CalcCombinationsBlockSingleValidation(ABlock, AStartIndex)
// Multiple validation numbers assigned to this block. Checks cache first.
else begin
validationsId := CalcValidationsId(AStartIndex, AStopIndex);
if not ABlock.CombinationsCache.TryGetValue(validationsId, Result) then
begin
Result := 0;
// Assigns validation numbers to specific damages.
damageToValidationAssignments := TDamageToValidationAssignments.Create(Self, ABlock, AStartIndex, AStopIndex);
for indices in damageToValidationAssignments do
Result := Result + CalcCombinationsBlockMultiValidations(ABlock, indices, AStartIndex, AStopIndex);
damageToValidationAssignments.Free;
ABlock.CombinationsCache.Add(validationsId, Result);
end;
end;
end;
function TConditionRecord.CalcCombinationsWildcardSequence(const ASequenceLength, AStartIndex, AStopIndex: Integer):
Int64;
var
count, freedoms: Integer;
begin
if AStartIndex < AStopIndex + 1 then
begin
count := AStopIndex + 1 - AStartIndex;
freedoms := ASequenceLength - FValidationLengths[AStartIndex, AStopIndex + 1];
if freedoms >= 0 then
Result := BinomialCoefficients.Get(count + freedoms, freedoms)
else
Result := 0;
end
else
Result := 1;
end;
{ THotSprings }
constructor THotSprings.Create; constructor THotSprings.Create;
begin begin
FValidation := specialize TList<Integer>.Create; FCombinationsCache := TCombinationsCache.Create([doOwnsValues]);
end; end;
destructor THotSprings.Destroy; destructor THotSprings.Destroy;
begin begin
FValidation.Free; FCombinationsCache.Free;
inherited Destroy; inherited Destroy;
end; end;
procedure THotSprings.ProcessDataLine(const ALine: string); procedure THotSprings.ProcessDataLine(const ALine: string);
var var
split: TStringArray; conditionRecord1, conditionRecord2: TConditionRecord;
i, val, maxFreeOperationalCount: Integer; mainSplit, split: TStringArray;
part, unfolded: string;
i: Integer;
begin begin
FValidation.Clear; conditionRecord1 := TConditionRecord.Create(FCombinationsCache);
split := ALine.Split([' ', ',']); conditionRecord2 := TConditionRecord.Create(FCombinationsCache);
FSpringPattern := split[0];
maxFreeOperationalCount := Length(FSpringPattern) - Length(split) + 2; mainSplit := ALine.Split([' ']);
for i := 1 to Length(split) - 1 do
begin
val := StrToInt(split[i]);
FValidation.Add(val);
Dec(maxFreeOperationalCount, val);
end;
ExtendArrangement('', maxFreeOperationalCount, 0); // Adds blocks for part 1.
conditionRecord1.AddBlocks(mainSplit[0]);
// Adds blocks for part 2.
unfolded := mainSplit[0];
for i := 2 to CPart2Repetition do
unfolded := unfolded + CWildcardChar + mainSplit[0];
conditionRecord2.AddBlocks(unfolded);
// Adds validation numbers.
split := mainSplit[1].Split([',']);
for part in split do
conditionRecord1.Validation.Add(StrToInt(part));
for i := 1 to CPart2Repetition do
conditionRecord2.Validation.AddRange(conditionRecord1.Validation);
FPart1 := FPart1 + conditionRecord1.GenerateBlockAssignments;
FPart2 := FPart2 + conditionRecord2.GenerateBlockAssignments;
conditionRecord1.Free;
conditionRecord2.Free;
end; end;
procedure THotSprings.Finish; procedure THotSprings.Finish;

View File

@ -152,6 +152,10 @@
<Filename Value="USnowverloadTestCases.pas"/> <Filename Value="USnowverloadTestCases.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit> </Unit>
<Unit>
<Filename Value="UBinomialCoefficientsTestCases.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
</Units> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

View File

@ -10,7 +10,7 @@ uses
UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases, UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases,
UPulsePropagationTestCases, UStepCounterTestCases, USandSlabsTestCases, ULongWalkTestCases, UPulsePropagationTestCases, UStepCounterTestCases, USandSlabsTestCases, ULongWalkTestCases,
UNeverTellMeTheOddsTestCases, USnowverloadTestCases, UBigIntTestCases, UPolynomialTestCases, UNeverTellMeTheOddsTestCases, USnowverloadTestCases, UBigIntTestCases, UPolynomialTestCases,
UPolynomialRootsTestCases; UPolynomialRootsTestCases, UBinomialCoefficientsTestCases;
{$R *.res} {$R *.res}

View File

@ -0,0 +1,138 @@
{
Solutions to the Advent Of Code.
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
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
}
unit UBinomialCoefficientsTestCases;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, fpcunit, testregistry, UBinomialCoefficients;
type
{ TBinomialCoefficientsTestCase }
TBinomialCoefficientsTestCase = class(TTestCase)
private
FBinomialCoefficientCache: TBinomialCoefficientCache;
procedure RunRangeError;
procedure AssertEqualsCalculation(const AN, AK, AExpected: Cardinal);
procedure AssertEqualsCachedRowsCount(const AExpected: Cardinal);
protected
procedure SetUp; override;
procedure TearDown; override;
published
procedure TestZeroChooseZero;
procedure TestNChooseZero;
procedure TestNChooseN;
procedure TestNChooseK;
procedure TestCombined;
procedure TestFullRow;
procedure TestRangeError;
end;
implementation
{ TBinomialCoefficientsTestCase }
procedure TBinomialCoefficientsTestCase.RunRangeError;
begin
FBinomialCoefficientCache.Get(1, 5);
end;
procedure TBinomialCoefficientsTestCase.AssertEqualsCalculation(const AN, AK, AExpected: Cardinal);
begin
AssertEquals('Unexpected calculation result', AExpected, FBinomialCoefficientCache.Get(AN, AK));
end;
procedure TBinomialCoefficientsTestCase.AssertEqualsCachedRowsCount(const AExpected: Cardinal);
begin
AssertEquals('Unexpected cached rows count', AExpected, FBinomialCoefficientCache.GetCachedRowsCount);
end;
procedure TBinomialCoefficientsTestCase.SetUp;
begin
FBinomialCoefficientCache := TBinomialCoefficientCache.Create;
end;
procedure TBinomialCoefficientsTestCase.TearDown;
begin
FBinomialCoefficientCache.Free;
end;
procedure TBinomialCoefficientsTestCase.TestZeroChooseZero;
begin
AssertEqualsCalculation(0, 0, 1);
AssertEqualsCachedRowsCount(1);
end;
procedure TBinomialCoefficientsTestCase.TestNChooseZero;
begin
AssertEqualsCalculation(15, 0, 1);
AssertEqualsCachedRowsCount(16);
end;
procedure TBinomialCoefficientsTestCase.TestNChooseN;
begin
AssertEqualsCalculation(11, 11, 1);
AssertEqualsCachedRowsCount(12);
end;
procedure TBinomialCoefficientsTestCase.TestNChooseK;
begin
AssertEqualsCalculation(8, 3, 56);
AssertEqualsCachedRowsCount(9);
end;
procedure TBinomialCoefficientsTestCase.TestCombined;
begin
AssertEqualsCalculation(5, 1, 5);
AssertEqualsCachedRowsCount(6);
AssertEqualsCalculation(8, 4, 70);
AssertEqualsCachedRowsCount(9);
AssertEqualsCalculation(3, 1, 3);
AssertEqualsCachedRowsCount(9);
end;
procedure TBinomialCoefficientsTestCase.TestFullRow;
begin
AssertEqualsCalculation(5, 0, 1);
AssertEqualsCachedRowsCount(6);
AssertEqualsCalculation(5, 1, 5);
AssertEqualsCachedRowsCount(6);
AssertEqualsCalculation(5, 2, 10);
AssertEqualsCachedRowsCount(6);
AssertEqualsCalculation(5, 3, 10);
AssertEqualsCachedRowsCount(6);
AssertEqualsCalculation(5, 4, 5);
AssertEqualsCachedRowsCount(6);
AssertEqualsCalculation(5, 5, 1);
AssertEqualsCachedRowsCount(6);
end;
procedure TBinomialCoefficientsTestCase.TestRangeError;
begin
AssertException(ERangeError, @RunRangeError);
end;
initialization
RegisterTest('Helper', TBinomialCoefficientsTestCase);
end.

View File

@ -33,6 +33,7 @@ type
function CreateSolver: ISolver; override; function CreateSolver: ISolver; override;
published published
procedure TestPart1; procedure TestPart1;
procedure TestPart2;
end; end;
{ THotSpringsTestCase } { THotSpringsTestCase }
@ -40,14 +41,20 @@ type
THotSpringsTestCase = class(TSolverTestCase) THotSpringsTestCase = class(TSolverTestCase)
protected protected
function CreateSolver: ISolver; override; function CreateSolver: ISolver; override;
procedure TestSingleLine(const ALine: string; const AValue: Integer); procedure TestSingleLine(const ALine: string);
published published
procedure TestExampleLine1; procedure TestExampleLine1Part1;
procedure TestExampleLine2; procedure TestExampleLine2Part1;
procedure TestExampleLine3; procedure TestExampleLine3Part1;
procedure TestExampleLine4; procedure TestExampleLine4Part1;
procedure TestExampleLine5; procedure TestExampleLine5Part1;
procedure TestExampleLine6; procedure TestExampleLine6Part1;
procedure TestExampleLine1Part2;
procedure TestExampleLine2Part2;
procedure TestExampleLine3Part2;
procedure TestExampleLine4Part2;
procedure TestExampleLine5Part2;
procedure TestExampleLine6Part2;
end; end;
implementation implementation
@ -64,6 +71,11 @@ begin
AssertEquals(21, FSolver.GetResultPart1); AssertEquals(21, FSolver.GetResultPart1);
end; end;
procedure THotSpringsExampleTestCase.TestPart2;
begin
AssertEquals(525152, FSolver.GetResultPart2);
end;
{ THotSpringsTestCase } { THotSpringsTestCase }
function THotSpringsTestCase.CreateSolver: ISolver; function THotSpringsTestCase.CreateSolver: ISolver;
@ -71,42 +83,83 @@ begin
Result := THotSprings.Create; Result := THotSprings.Create;
end; end;
procedure THotSpringsTestCase.TestSingleLine(const ALine: string; const AValue: Integer); procedure THotSpringsTestCase.TestSingleLine(const ALine: string);
begin begin
FSolver.Init; FSolver.Init;
FSolver.ProcessDataLine(ALine); FSolver.ProcessDataLine(ALine);
FSolver.Finish; FSolver.Finish;
AssertEquals(AValue, FSolver.GetResultPart1);
end; end;
procedure THotSpringsTestCase.TestExampleLine1; procedure THotSpringsTestCase.TestExampleLine1Part1;
begin begin
TestSingleLine('???.### 1,1,3', 1); TestSingleLine('???.### 1,1,3');
AssertEquals(1, FSolver.GetResultPart1);
end; end;
procedure THotSpringsTestCase.TestExampleLine2; procedure THotSpringsTestCase.TestExampleLine2Part1;
begin begin
TestSingleLine('.??..??...?##. 1,1,3', 4); TestSingleLine('.??..??...?##. 1,1,3');
AssertEquals(4, FSolver.GetResultPart1);
end; end;
procedure THotSpringsTestCase.TestExampleLine3; procedure THotSpringsTestCase.TestExampleLine3Part1;
begin begin
TestSingleLine('?#?#?#?#?#?#?#? 1,3,1,6', 1); TestSingleLine('?#?#?#?#?#?#?#? 1,3,1,6');
AssertEquals(1, FSolver.GetResultPart1);
end; end;
procedure THotSpringsTestCase.TestExampleLine4; procedure THotSpringsTestCase.TestExampleLine4Part1;
begin begin
TestSingleLine('????.#...#... 4,1,1', 1); TestSingleLine('????.#...#... 4,1,1');
AssertEquals(1, FSolver.GetResultPart1);
end; end;
procedure THotSpringsTestCase.TestExampleLine5; procedure THotSpringsTestCase.TestExampleLine5Part1;
begin begin
TestSingleLine('????.######..#####. 1,6,5', 4); TestSingleLine('????.######..#####. 1,6,5');
AssertEquals(4, FSolver.GetResultPart1);
end; end;
procedure THotSpringsTestCase.TestExampleLine6; procedure THotSpringsTestCase.TestExampleLine6Part1;
begin begin
TestSingleLine('?###???????? 3,2,1', 10); TestSingleLine('?###???????? 3,2,1');
AssertEquals(10, FSolver.GetResultPart1);
end;
procedure THotSpringsTestCase.TestExampleLine1Part2;
begin
TestSingleLine('???.### 1,1,3');
AssertEquals(1, FSolver.GetResultPart2);
end;
procedure THotSpringsTestCase.TestExampleLine2Part2;
begin
TestSingleLine('.??..??...?##. 1,1,3');
AssertEquals(16384, FSolver.GetResultPart2);
end;
procedure THotSpringsTestCase.TestExampleLine3Part2;
begin
TestSingleLine('?#?#?#?#?#?#?#? 1,3,1,6');
AssertEquals(1, FSolver.GetResultPart2);
end;
procedure THotSpringsTestCase.TestExampleLine4Part2;
begin
TestSingleLine('????.#...#... 4,1,1');
AssertEquals(16, FSolver.GetResultPart2);
end;
procedure THotSpringsTestCase.TestExampleLine5Part2;
begin
TestSingleLine('????.######..#####. 1,6,5');
AssertEquals(2500, FSolver.GetResultPart2);
end;
procedure THotSpringsTestCase.TestExampleLine6Part2;
begin
TestSingleLine('?###???????? 3,2,1');
AssertEquals(506250, FSolver.GetResultPart2);
end; end;
initialization initialization