diff --git a/AdventOfCode.lpi b/AdventOfCode.lpi
index 2a9af4b..74c9eb4 100644
--- a/AdventOfCode.lpi
+++ b/AdventOfCode.lpi
@@ -157,6 +157,14 @@
+
+
+
+
+
+
+
+
diff --git a/UBinomialCoefficients.pas b/UBinomialCoefficients.pas
new file mode 100644
index 0000000..71c4780
--- /dev/null
+++ b/UBinomialCoefficients.pas
@@ -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 .
+}
+
+unit UBinomialCoefficients;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Generics.Collections;
+
+type
+ TCardinalArray = array of Cardinal;
+ TCardinalArrays = specialize TList;
+
+ { 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.
+
diff --git a/UCommon.pas b/UCommon.pas
index bae26e7..b02c18e 100644
--- a/UCommon.pas
+++ b/UCommon.pas
@@ -40,6 +40,7 @@ const
CPCardinalDirections: array[0..3] of PPoint = (@CDirectionRight, @CDirectionDown, @CDirectionLeft, @CDirectionUp);
type
+ TInt64Array = array of Int64;
TIntegerList = specialize TList;
TPoints = specialize TList;
diff --git a/UMultiIndexEnumerator.pas b/UMultiIndexEnumerator.pas
new file mode 100644
index 0000000..67dc020
--- /dev/null
+++ b/UMultiIndexEnumerator.pas
@@ -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 .
+}
+
+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)
+ 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)
+ public
+ function GetEnumerator: specialize IEnumerator;
+ // 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;
+begin
+ Result := TMultiIndexEnumerator.Create(Self);
+end;
+
+end.
+
diff --git a/solvers/UHotSprings.pas b/solvers/UHotSprings.pas
index 8f12cab..0078c88 100644
--- a/solvers/UHotSprings.pas
+++ b/solvers/UHotSprings.pas
@@ -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,27 +22,176 @@ unit UHotSprings;
interface
uses
- Classes, SysUtils, Generics.Collections, USolver;
+ Classes, SysUtils, Math, Generics.Collections, USolver, UCommon, UMultiIndexEnumerator, UBinomialCoefficients;
const
COperationalChar = '.';
CDamagedChar = '#';
CWildcardChar = '?';
- COperationalPatternChars = [COperationalChar, CWildcardChar];
- CDamagedPatternChars = [CDamagedChar, CWildcardChar];
+ CPart2Repetition = 5;
type
+ TValidationLengths = array of array of Integer;
+
+ { TDamage }
+
+ TDamage = record
+ Start, Length, CharsRemaining: Integer;
+ end;
+ TDamages = specialize TList;
+
+ TBlockCombinationsCache = specialize THashMap;
+ TCombinationsCache = specialize TObjectHashMap;
+
+ { 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;
+
+ { 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;
+
+ { 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 = class(TSolver)
private
- FValidation: specialize TList;
- 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;
+ FCombinationsCache: TCombinationsCache;
public
constructor Create;
destructor Destroy; override;
@@ -54,99 +203,549 @@ type
implementation
-{ THotSprings }
+{ TBlock }
-procedure THotSprings.ExtendArrangement(const AArrangement: string; const ARemainingFreeOperationalCount,
- 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;
+procedure TBlock.ParseDamages;
var
i, len: Integer;
+ damage: TDamage;
begin
- Result := True;
- len := Length(AArrangement);
- for i := 1 to ALength do
+ 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
- if FSpringPattern[len + i] in CDamagedPatternChars then
- AArrangement := AArrangement + CDamagedChar
+ 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
- Result := False;
- Break;
+ 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
+ Result := True;
+ if ACurrentIndex + 1 = GetCardinality then
+ 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
+ if ACurrentIndexArray[ACurrentIndex] > FConditionRecord.Validation.Count then
+ Result := ivrBacktrack
+ else begin
+ if ACurrentIndex > 0 then
+ 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;
+
+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;
begin
- FValidation := specialize TList.Create;
+ FCombinationsCache := TCombinationsCache.Create([doOwnsValues]);
end;
destructor THotSprings.Destroy;
begin
- FValidation.Free;
+ FCombinationsCache.Free;
inherited Destroy;
end;
procedure THotSprings.ProcessDataLine(const ALine: string);
var
- split: TStringArray;
- i, val, maxFreeOperationalCount: Integer;
+ conditionRecord1, conditionRecord2: TConditionRecord;
+ mainSplit, split: TStringArray;
+ part, unfolded: string;
+ i: Integer;
begin
- FValidation.Clear;
- split := ALine.Split([' ', ',']);
- FSpringPattern := split[0];
+ conditionRecord1 := TConditionRecord.Create(FCombinationsCache);
+ conditionRecord2 := TConditionRecord.Create(FCombinationsCache);
- 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;
+ mainSplit := ALine.Split([' ']);
- 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;
procedure THotSprings.Finish;
diff --git a/tests/AdventOfCodeFPCUnit.lpi b/tests/AdventOfCodeFPCUnit.lpi
index 0d0c019..7ee1927 100644
--- a/tests/AdventOfCodeFPCUnit.lpi
+++ b/tests/AdventOfCodeFPCUnit.lpi
@@ -152,6 +152,10 @@
+
+
+
+
diff --git a/tests/AdventOfCodeFPCUnit.lpr b/tests/AdventOfCodeFPCUnit.lpr
index 2c2a60e..4b270c1 100644
--- a/tests/AdventOfCodeFPCUnit.lpr
+++ b/tests/AdventOfCodeFPCUnit.lpr
@@ -10,7 +10,7 @@ uses
UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases,
UPulsePropagationTestCases, UStepCounterTestCases, USandSlabsTestCases, ULongWalkTestCases,
UNeverTellMeTheOddsTestCases, USnowverloadTestCases, UBigIntTestCases, UPolynomialTestCases,
- UPolynomialRootsTestCases;
+ UPolynomialRootsTestCases, UBinomialCoefficientsTestCases;
{$R *.res}
diff --git a/tests/UBinomialCoefficientsTestCases.pas b/tests/UBinomialCoefficientsTestCases.pas
new file mode 100644
index 0000000..4de87b4
--- /dev/null
+++ b/tests/UBinomialCoefficientsTestCases.pas
@@ -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 .
+}
+
+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.
+
diff --git a/tests/UHotSpringsTestCases.pas b/tests/UHotSpringsTestCases.pas
index 03f9a92..cac94b0 100644
--- a/tests/UHotSpringsTestCases.pas
+++ b/tests/UHotSpringsTestCases.pas
@@ -33,6 +33,7 @@ type
function CreateSolver: ISolver; override;
published
procedure TestPart1;
+ procedure TestPart2;
end;
{ THotSpringsTestCase }
@@ -40,14 +41,20 @@ type
THotSpringsTestCase = class(TSolverTestCase)
protected
function CreateSolver: ISolver; override;
- procedure TestSingleLine(const ALine: string; const AValue: Integer);
+ procedure TestSingleLine(const ALine: string);
published
- procedure TestExampleLine1;
- procedure TestExampleLine2;
- procedure TestExampleLine3;
- procedure TestExampleLine4;
- procedure TestExampleLine5;
- procedure TestExampleLine6;
+ procedure TestExampleLine1Part1;
+ procedure TestExampleLine2Part1;
+ procedure TestExampleLine3Part1;
+ procedure TestExampleLine4Part1;
+ procedure TestExampleLine5Part1;
+ procedure TestExampleLine6Part1;
+ procedure TestExampleLine1Part2;
+ procedure TestExampleLine2Part2;
+ procedure TestExampleLine3Part2;
+ procedure TestExampleLine4Part2;
+ procedure TestExampleLine5Part2;
+ procedure TestExampleLine6Part2;
end;
implementation
@@ -64,6 +71,11 @@ begin
AssertEquals(21, FSolver.GetResultPart1);
end;
+procedure THotSpringsExampleTestCase.TestPart2;
+begin
+ AssertEquals(525152, FSolver.GetResultPart2);
+end;
+
{ THotSpringsTestCase }
function THotSpringsTestCase.CreateSolver: ISolver;
@@ -71,42 +83,83 @@ begin
Result := THotSprings.Create;
end;
-procedure THotSpringsTestCase.TestSingleLine(const ALine: string; const AValue: Integer);
+procedure THotSpringsTestCase.TestSingleLine(const ALine: string);
begin
FSolver.Init;
FSolver.ProcessDataLine(ALine);
FSolver.Finish;
- AssertEquals(AValue, FSolver.GetResultPart1);
end;
-procedure THotSpringsTestCase.TestExampleLine1;
+procedure THotSpringsTestCase.TestExampleLine1Part1;
begin
- TestSingleLine('???.### 1,1,3', 1);
+ TestSingleLine('???.### 1,1,3');
+ AssertEquals(1, FSolver.GetResultPart1);
end;
-procedure THotSpringsTestCase.TestExampleLine2;
+procedure THotSpringsTestCase.TestExampleLine2Part1;
begin
- TestSingleLine('.??..??...?##. 1,1,3', 4);
+ TestSingleLine('.??..??...?##. 1,1,3');
+ AssertEquals(4, FSolver.GetResultPart1);
end;
-procedure THotSpringsTestCase.TestExampleLine3;
+procedure THotSpringsTestCase.TestExampleLine3Part1;
begin
- TestSingleLine('?#?#?#?#?#?#?#? 1,3,1,6', 1);
+ TestSingleLine('?#?#?#?#?#?#?#? 1,3,1,6');
+ AssertEquals(1, FSolver.GetResultPart1);
end;
-procedure THotSpringsTestCase.TestExampleLine4;
+procedure THotSpringsTestCase.TestExampleLine4Part1;
begin
- TestSingleLine('????.#...#... 4,1,1', 1);
+ TestSingleLine('????.#...#... 4,1,1');
+ AssertEquals(1, FSolver.GetResultPart1);
end;
-procedure THotSpringsTestCase.TestExampleLine5;
+procedure THotSpringsTestCase.TestExampleLine5Part1;
begin
- TestSingleLine('????.######..#####. 1,6,5', 4);
+ TestSingleLine('????.######..#####. 1,6,5');
+ AssertEquals(4, FSolver.GetResultPart1);
end;
-procedure THotSpringsTestCase.TestExampleLine6;
+procedure THotSpringsTestCase.TestExampleLine6Part1;
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;
initialization