{
Solutions to the Advent Of Code.
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
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 UHotSprings;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, Math, Generics.Collections, USolver, UCommon, UMultiIndexEnumerator, UBinomialCoefficients;
const
COperationalChar = '.';
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
FCombinationsCache: TCombinationsCache;
public
constructor Create;
destructor Destroy; override;
procedure ProcessDataLine(const ALine: string); override;
procedure Finish; override;
function GetDataFileName: string; override;
function GetPuzzleName: string; override;
end;
implementation
{ TBlock }
procedure TBlock.ParseDamages;
var
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
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
FCombinationsCache := TCombinationsCache.Create([doOwnsValues]);
end;
destructor THotSprings.Destroy;
begin
FCombinationsCache.Free;
inherited Destroy;
end;
procedure THotSprings.ProcessDataLine(const ALine: string);
var
conditionRecord1, conditionRecord2: TConditionRecord;
mainSplit, split: TStringArray;
part, unfolded: string;
i: Integer;
begin
conditionRecord1 := TConditionRecord.Create(FCombinationsCache);
conditionRecord2 := TConditionRecord.Create(FCombinationsCache);
mainSplit := ALine.Split([' ']);
// 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;
begin
end;
function THotSprings.GetDataFileName: string;
begin
Result := 'hot_springs.txt';
end;
function THotSprings.GetPuzzleName: string;
begin
Result := 'Day 12: Hot Springs';
end;
end.