Compare commits

...

8 Commits

9 changed files with 1178 additions and 96 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>

96
UBinomialCoefficients.pas Normal file
View File

@ -0,0 +1,96 @@
{
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;
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;
end.

View File

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

160
UMultiIndexEnumerator.pas Normal file
View File

@ -0,0 +1,160 @@
{
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>;
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
@ -21,28 +21,128 @@ unit UHotSprings;
interface interface
// TODO: Remove this and the ifdefs.
{$define debug}
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;
// TODO: TIntegerArray probably not needed.
TIntegerArray = array of Integer;
{ TDamage }
TDamage = record
Start, Length, CharsRemaining: Integer;
end;
TDamages = specialize TList<TDamage>;
// TODO: Instead of using TDamagesBlocks, "block" should be a record of a string and its associated list TDamages.
TDamagesBlocks = specialize TObjectList<TDamages>;
{ TValidationToDamageAssignments }
TValidationToDamageAssignments = class(TEnumerableMultiIndexStrategy)
private
FValidation: TIntegerList;
FValidationLengths: TValidationLengths;
FDamages: TDamages;
FValidationStartIndex, FValidationStopIndex: Integer;
// Calculates "span", the length of all damages for this validation number combined.
function CalcValidationSpan(constref ACurrentIndexArray: TIndexArray; const ALastDamageIndex, AValidationNumber:
Integer): Integer;
public
constructor Create(constref AValidation: TIntegerList; constref AValidationLengths: TValidationLengths;
constref ADamages: TDamages; const AStartIndex, AStopIndex: 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 }
TValidationPositionOffsets = class(TEnumerableMultiIndexStrategy)
private
FValidation: TIntegerList;
FPositionInfos: TValidationPositionInfos;
public
constructor Create(constref AValidation: TIntegerList; constref APositionInfos: TValidationPositionInfos);
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
FBinomialCoefficients: TBinomialCoefficientCache;
FValidation: TIntegerList;
// List of non-empty, maximum-length parts of the pattern without operational springs ("blocks").
FBlocks: TStringList;
// 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: TIntegerArray;
// List 'a' of lists of damages in a block. Each list of damages 'a[i]' contains exactly one entry for each block of
// consecutive damages characters in the i-th block.
// For example, if the pattern is '?#.??##?#?..??', then 'FDamagesBlocks' would have 3 entries, which are lists of
// 1, 2, and 0 damages, respectively.
FDamagesBlocks: TDamagesBlocks;
procedure InitValidationLengths;
procedure InitMinIndices;
function CalcCombinations(constref AIndices: TIntegerArray): Int64;
function CalcCombinationsBlock(const ABlock: string; constref ADamages: TDamages; const AStartIndex, AStopIndex:
Integer): Int64;
function CalcCombinationsBlockSingleValidation(const ABlockLength: Integer; constref ADamages: TDamages;
const AIndex: Integer): Int64;
function CalcCombinationsBlockMultiValidations(const ABlockLength: Integer; constref ADamages: TDamages;
constref AIndices: TIndexArray; const AStartIndex, AStopIndex: Integer): Int64;
function CalcCombinationsBlockAssignedValidations(const ABlockLength: Integer; constref APositionInfos:
TValidationPositionInfos; constref AOffsets: TIndexArray; const AStartIndex, AStopIndex: Integer): Int64;
function CalcCombinationsWildcardSequence(const ASequenceLength, AStartIndex, AStopIndex: Integer): Int64;
function ParseDamages(const ABlock: string): TDamages;
public
property Blocks: TStringList read FBlocks;
property Validation: TIntegerList read FValidation;
constructor Create(constref ABinomialCoefficients: TBinomialCoefficientCache);
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;
end;
{ THotSprings } { THotSprings }
THotSprings = class(TSolver) THotSprings = class(TSolver)
private private
FValidation: specialize TList<Integer>; // Keeping the binomial coefficients calculator here so it can be shared for all lines.
FSpringPattern: string; FBinomialCoefficients: TBinomialCoefficientCache;
procedure ExtendArrangement(const AArrangement: string; const ARemainingFreeOperationalCount, ACurrentValidationIndex: // TODO: Remove FDebugIndex.
Integer); FDebugIndex: 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 +154,604 @@ type
implementation implementation
{ THotSprings } { TValidationToDamageAssignments }
procedure THotSprings.ExtendArrangement(const AArrangement: string; const ARemainingFreeOperationalCount, function TValidationToDamageAssignments.CalcValidationSpan(constref ACurrentIndexArray: TIndexArray;
ACurrentValidationIndex: Integer); const ALastDamageIndex, AValidationNumber: Integer): Integer;
var var
match: Boolean; spanStart: Integer;
temp: string;
begin begin
if Length(AArrangement) = Length(FSpringPattern) then spanStart := ALastDamageIndex;
Inc(FPart1) while (spanStart > 0) and (ACurrentIndexArray[spanStart - 1] = AValidationNumber) do
else begin Dec(spanStart);
temp := AArrangement; Result := FDamages[ALastDamageIndex].Length;
// Tries to append a dot (operational) to the current arrangement. if spanStart < ALastDamageIndex then
if (ARemainingFreeOperationalCount > 0) and TryAppendOperationalChar(temp) then Inc(Result, FDamages[ALastDamageIndex].Start - FDamages[spanStart].Start);
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; end;
function THotSprings.TryAppendOperationalChar(var AArrangement: string): Boolean; constructor TValidationToDamageAssignments.Create(constref AValidation: TIntegerList; constref AValidationLengths:
TValidationLengths; constref ADamages: TDamages; const AStartIndex, AStopIndex: Integer);
begin begin
if FSpringPattern[Length(AArrangement) + 1] in COperationalPatternChars then FValidation := AValidation;
begin FValidationLengths := AValidationLengths;
AArrangement := AArrangement + COperationalChar; FDamages := ADamages;
Result := True; FValidationStartIndex := AStartIndex;
end FValidationStopIndex := AStopIndex;
else
Result := False;
end; end;
function THotSprings.TryAppendValidationBlock(var AArrangement: string; const ALength: Integer): Boolean; function TValidationToDamageAssignments.GetCardinality: Integer;
var begin
i, len: Integer; Result := FDamages.Count;
end;
function TValidationToDamageAssignments.TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray;
const ACurrentIndex: Integer; out AStartIndexValue: Integer): Boolean;
begin begin
Result := True; Result := True;
len := Length(AArrangement); if ACurrentIndex > 0 then
for i := 1 to ALength do AStartIndexValue := ACurrentIndexArray[ACurrentIndex - 1]
else
AStartIndexValue := FValidationStartIndex;
end;
function TValidationToDamageAssignments.ValidateIndexValue(constref ACurrentIndexArray: TIndexArray;
const ACurrentIndex: Integer): TIndexValidationResult;
var
i, prev, firstSkip: Integer;
begin
i := ACurrentIndexArray[ACurrentIndex];
if i > FValidationStopIndex then
begin begin
if FSpringPattern[len + i] in CDamagedPatternChars then Result := ivrBacktrack;
AArrangement := AArrangement + CDamagedChar Exit;
end;
// Checks if there is enough space after this damage for remaining validation numbers.
if (i < FValidationStopIndex)
and (FValidationLengths[i + 1, FValidationStopIndex + 1] + 1 > FDamages[ACurrentIndex].CharsRemaining) then
begin
Result := ivrSkip;
Exit;
end;
// Checks if there is enough space before this damage for previous validation numbers.
if (FValidationStartIndex < i)
and (FValidationLengths[FValidationStartIndex, i] + 1 >= FDamages[ACurrentIndex].Start) then
begin
Result := ivrBacktrack;
Exit;
end;
// Checks if there is enough space between previous and this damage for skipped validation numbers.
if ACurrentIndex > 0 then
begin
prev := ACurrentIndex - 1;
firstSkip := ACurrentIndexArray[prev] + 1;
if (firstSkip < i) and (FValidationLengths[firstSkip, i] + 2 > FDamages[ACurrentIndex].Start - FDamages[prev].Start - FDamages[prev].Length) then
begin
Result := ivrBacktrack;
Exit;
end;
end;
// Checks if span is small enough to fit within this validation number.
if FValidation[i] < CalcValidationSpan(ACurrentIndexArray, ACurrentIndex, i) then
begin
Result := ivrSkip;
Exit;
end;
Result := ivrValid;
end;
{ TValidationPositionOffsets }
constructor TValidationPositionOffsets.Create(constref AValidation: TIntegerList; constref APositionInfos:
TValidationPositionInfos);
begin
FValidation := AValidation;
FPositionInfos := APositionInfos;
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];
// Calculates start value such that the validation number just includes MinEnd.
//AStartIndexValue := info.MinEnd - FValidation[info.ValidationIndex] + 1;
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] + FValidation[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;
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]);
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]);
end;
end;
function TConditionRecord.CalcCombinations(constref AIndices: TIntegerArray): Int64;
var
i, j: Integer;
// TODO: Remove r.
r: Int64;
begin
{$ifdef debug}
for i in AIndices do
Write(i, ' ');
WriteLn;
{$endif}
Result := 1;
i := 0;
while (Result > 0) and (i < FBlocks.Count) do
begin
if FDamagesBlocks[i].Count > 0 then
r := CalcCombinationsBlock(FBlocks[i], FDamagesBlocks[i], AIndices[i], AIndices[i + 1] - 1)
else begin else begin
Result := False; {$ifdef debug}
Break; Write(' ', FBlocks[i], ' ');
for j := AIndices[i] to AIndices[i + 1] - 1 do
Write(FValidation[j], ' ');
WriteLn;
Write(' count/space/freedoms: ');
{$endif}
r := CalcCombinationsWildcardSequence(Length(FBlocks[i]), AIndices[i], AIndices[i + 1] - 1);
{$ifdef debug}
WriteLn(' result: ', r);
{$endif}
end;
{$ifdef debug}
WriteLn(' Result: ', r);
{$endif}
Result := Result * r;
Inc(i);
end;
end;
function TConditionRecord.CalcCombinationsBlock(const ABlock: string; constref ADamages: TDamages; const AStartIndex,
AStopIndex: Integer): Int64;
var
i, j, k: Integer;
indices: TIndexArray;
validationToDamageAssignments: TValidationToDamageAssignments;
begin
{$ifdef debug}
Write(' ', ABlock, ' ');
for i := AStartIndex to AStopIndex do
Write(FValidation[i], ' ');
WriteLn;
{$endif}
// No validation number assigned to this block.
if AStartIndex > AStopIndex then
begin
if ADamages.Count = 0 then
Result := 1
else
Result := 0;
end
// One validation number assigned to this block.
else if AStartIndex = AStopIndex then
Result := CalcCombinationsBlockSingleValidation(Length(ABlock), ADamages, AStartIndex)
// Multiple validation numbers assigned to this block.
else begin
{$ifdef debug}
Write(' min before: ');
for i := AStartIndex to AStopIndex do
Write(FValidationLengths[AStartIndex, i + 1] - FValidation[i], ' ');
WriteLn;
Write(' min after: ');
for i := AStartIndex to AStopIndex do
Write(FValidationLengths[i, AStopIndex + 1] - FValidation[i], ' ');
WriteLn;
for i := 0 to ADamages.Count - 1 do
begin
WriteLn(' damage: start ',ADamages[i].Start, ', length ', ADamages[i].Length, ', remain ', ADamages[i].CharsRemaining);
Write(' ');
for j := AStartIndex to AStopIndex do
// Enough space before damage for the other validation numbers?
if (FValidationLengths[AStartIndex, j + 1] - FValidation[j] < ADamages[i].Start)
// Enough space after damage for the other validation numbers?
and (FValidationLengths[j, AStopIndex + 1] - FValidation[j] <= ADamages[i].CharsRemaining)
// Damage itself small enough for this validation number?
and (FValidation[j] >= ADamages[i].Length) then
Write(j - AStartIndex, ' ');
WriteLn;
end;
{$endif}
Result := 0;
// Assigns validation numbers to specific damages.
validationToDamageAssignments := TValidationToDamageAssignments.Create(FValidation, FValidationLengths, ADamages,
AStartIndex, AStopIndex);
{$ifdef debug}
WriteLn(' validation numbers (indices) per damages:');
{$endif}
for indices in validationToDamageAssignments do
begin
{$ifdef debug}
Write(' ');
for i := 0 to ADamages.Count - 1 do
Write(FValidation[indices[i]], ' ');
Write('( ');
for i := 0 to ADamages.Count - 1 do
Write(indices[i] - AStartIndex, ' ');
WriteLn(')');
{$endif}
Result := Result + CalcCombinationsBlockMultiValidations(Length(ABlock), ADamages, indices, AStartIndex, AStopIndex);
end;
validationToDamageAssignments.Free;
end;
end;
function TConditionRecord.CalcCombinationsBlockSingleValidation(const ABlockLength: Integer; constref ADamages:
TDamages; const AIndex: Integer): Int64;
var
combinedDamagesLength: Integer;
begin
if ABlockLength < FValidation[AIndex] then
Result := 0
else if ADamages.Count = 0 then
Result := ABlockLength - FValidation[AIndex] + 1
else begin
combinedDamagesLength := ADamages.Last.Start + ADamages.Last.Length - ADamages.First.Start;
if FValidation[AIndex] < combinedDamagesLength then
Result := 0
else begin
Result := Min(Min(Min(
ADamages.First.Start,
FValidation[AIndex] - combinedDamagesLength + 1),
ABlockLength - FValidation[AIndex] + 1),
ADamages.Last.CharsRemaining + 1);
end; end;
end; end;
end; end;
function TConditionRecord.CalcCombinationsBlockMultiValidations(const ABlockLength: Integer; constref ADamages:
TDamages; 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 := ADamages[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, ADamages[i].Start - 1 - FValidation[position.ValidationIndex]);
position.MinStart := Max(position.MinStart,
ADamages[i - 1].Start + ADamages[i - 1].Length - FValidation[position.ValidationIndex]);
positions.Add(position);
// Initializes next info record.
position.ValidationIndex := AIndices[i];
position.MaxStart := ADamages[i].Start;
position.MinStart := position.MinStart + FValidationLengths[AIndices[i - 1], AIndices[i]] + 1; //FValidation[position.ValidationIndex - 1] + 1;
end;
// Finalizes last info record.
position.MaxStart := Min(position.MaxStart, ABlockLength + 1 - FValidation[position.ValidationIndex]);
position.MinStart := Max(position.MinStart,
ADamages[high].Start + ADamages[high].Length - FValidation[position.ValidationIndex]);
positions.Add(position);
{$ifdef debug}
WriteLn(' validation position infos');
for position in positions do
WriteLn(' ', position.ValidationIndex, ' ', position.MinStart, ' ', position.MaxStart);
WriteLn(' offsets');
{$endif}
Result := 0;
validationPositionOffsets := TValidationPositionOffsets.Create(FValidation, positions);
for offsets in validationPositionOffsets do
Result := Result + CalcCombinationsBlockAssignedValidations(ABlockLength, positions, offsets, AStartIndex, AStopIndex);
validationPositionOffsets.Free;
positions.Free;
end;
function TConditionRecord.CalcCombinationsBlockAssignedValidations(const ABlockLength: Integer; constref APositionInfos:
TValidationPositionInfos; constref AOffsets: TIndexArray; const AStartIndex, AStopIndex: Integer): Int64;
var
i, space: Integer;
begin
{$ifdef debug}
Write(' ');
for i in AOffsets do
Write(i, ' ');
Write(' count/space/freedoms: ');
{$endif}
space := AOffsets[0] - 2;
Result := CalcCombinationsWildcardSequence(space, AStartIndex, APositionInfos[0].ValidationIndex - 1);
if Result = 0 then begin
{$ifdef debug}
WriteLn(' result: ', Result);
{$endif}
Exit;
end;
for i := 0 to APositionInfos.Count - 2 do begin
space := AOffsets[i + 1] - AOffsets[i] - FValidation[APositionInfos[i].ValidationIndex] - 2;
Result := Result * CalcCombinationsWildcardSequence(space, APositionInfos[i].ValidationIndex + 1, APositionInfos[i + 1].ValidationIndex - 1);
if Result = 0 then begin
{$ifdef debug}
WriteLn(' result: ', Result);
{$endif}
Exit;
end;
end;
space := ABlockLength - AOffsets[APositionInfos.Count - 1] - FValidation[APositionInfos.Last.ValidationIndex];
Result := Result * CalcCombinationsWildcardSequence(space, APositionInfos.Last.ValidationIndex + 1, AStopIndex);
{$ifdef debug}
WriteLn(' result: ', Result);
{$endif}
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];
{$ifdef debug}
Write(count, '/', ASequenceLength, '/', freedoms, ' ');
{$endif}
if freedoms >= 0 then
Result := FBinomialCoefficients.Get(count + freedoms, freedoms)
else
Result := 0;
end
else begin
Result := 1;
{$ifdef debug}
Write('X ');
{$endif}
end;
end;
function TConditionRecord.ParseDamages(const ABlock: string): TDamages;
var
i, len: Integer;
damage: TDamage;
begin
Result := TDamages.Create;
damage.Length := 0;
len := Length(ABlock);
for i := 1 to len do
// The pattern must only contain damage and wildcard characters here.
if ABlock[i] = CDamagedChar then
begin
if damage.Length = 0 then
damage.Start := i;
Inc(damage.Length);
end
else if damage.Length > 0 then
begin
damage.CharsRemaining := len - damage.Start - damage.Length + 1;
Result.Add(damage);
damage.Length := 0;
end;
if damage.Length > 0 then
begin
damage.CharsRemaining := 0;
Result.Add(damage);
end;
end;
constructor TConditionRecord.Create(constref ABinomialCoefficients: TBinomialCoefficientCache);
begin
FBinomialCoefficients := ABinomialCoefficients;
FBlocks := TStringList.Create;
FValidation := TIntegerList.Create;
FDamagesBlocks := TDamagesBlocks.Create;
end;
destructor TConditionRecord.Destroy;
begin
FBlocks.Free;
FValidation.Free;
FDamagesBlocks.Free;
inherited Destroy;
end;
procedure TConditionRecord.AddBlocks(const APattern: string);
var
split: TStringArray;
part: string;
begin
split := APattern.Split([COperationalChar]);
for part in split do
if Length(part) > 0 then
begin
FBlocks.Add(part);
FDamagesBlocks.Add(ParseDamages(part));
end;
end;
function TConditionRecord.GenerateBlockAssignments: Int64;
var
indices: array of Integer;
i, j, k, high: Integer;
// TODO: Remove r, count, misses.
r: Int64;
count, misses: Integer;
begin
count := 0;
misses := 0;
// Each loop (each call to 'CalcCombinations') represents an independent set of arrangements, defined by 'indices',
// where specific validation numbers are assigned to specific block patterns.
//
// Here, 'indices[i]' denotes the index + 1 of the last validation number assigned to 'FBlockPattern[i]', and the
// index of the first validation number in 'FValidation' assigned to 'FBlockPattern[i + 1]'. If two consecutive values
// in 'indices' are the same, then the block in between has no numbers assigned to it.
//
// Note that 'indices[0] = 0' and 'indices[FBlockPatterns.Count] = FValidation.Count' are constant. Having these two
// numbers in the array simplifies the code a bit.
InitValidationLengths;
//FPatternLengths := CalcPatternLengths;
InitMinIndices;
SetLength(indices, FBlocks.Count + 1);
high := Length(indices) - 2;
indices[0] := 0;
indices[high + 1] := FValidation.Count;
// TODO: Use TMultiIndexEnumerator for this.
Result := 0;
k := 0;
repeat
i := k + 1;
while i <= high do
begin
indices[i] := Max(indices[i - 1], FMinIndices[i - 1]);
while FValidationLengths[indices[i - 1], indices[i]] > Length(FBlocks[i - 1]) do
begin
Dec(i);
Inc(indices[i]);
end;
Inc(i);
end;
Inc(count);
r := CalcCombinations(indices);
if r = 0 then
Inc(misses);
Result := Result + r;
k := high;
while (k > 0)
and ((indices[k] = FValidation.Count)
or (FValidationLengths[indices[k - 1], indices[k] + 1] > Length(FBlocks[k - 1]))) do
Dec(k);
Inc(indices[k]);
until k = 0;
WriteLn(' missed: ', misses, '/', count);
end;
{ THotSprings }
constructor THotSprings.Create; constructor THotSprings.Create;
begin begin
FValidation := specialize TList<Integer>.Create; FDebugIndex := 0;
FBinomialCoefficients := TBinomialCoefficientCache.Create;
end; end;
destructor THotSprings.Destroy; destructor THotSprings.Destroy;
begin begin
FValidation.Free; FBinomialCoefficients.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; {$ifdef debug}
split := ALine.Split([' ', ',']); WriteLn(ALine);
FSpringPattern := split[0]; WriteLn;
{$endif}
maxFreeOperationalCount := Length(FSpringPattern) - Length(split) + 2; conditionRecord1 := TConditionRecord.Create(FBinomialCoefficients);
for i := 1 to Length(split) - 1 do conditionRecord2 := TConditionRecord.Create(FBinomialCoefficients);
begin
val := StrToInt(split[i]);
FValidation.Add(val);
Dec(maxFreeOperationalCount, val);
end;
ExtendArrangement('', maxFreeOperationalCount, 0); 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);
WriteLn(FDebugIndex + 1);
Inc(FDebugIndex);
FPart1 := FPart1 + conditionRecord1.GenerateBlockAssignments;
FPart2 := FPart2 + conditionRecord2.GenerateBlockAssignments;
conditionRecord1.Free;
conditionRecord2.Free;
{$ifdef debug}
WriteLn('------------------------');
WriteLn;
{$endif}
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

@ -26,6 +26,16 @@ uses
type type
{ THotSpringsFullDataTestCase }
THotSpringsFullDataTestCase = class(TEngineBaseTest)
protected
function CreateSolver: ISolver; override;
published
procedure TestPart1;
procedure TestPart2;
end;
{ THotSpringsExampleTestCase } { THotSpringsExampleTestCase }
THotSpringsExampleTestCase = class(TExampleEngineBaseTest) THotSpringsExampleTestCase = class(TExampleEngineBaseTest)
@ -33,6 +43,7 @@ type
function CreateSolver: ISolver; override; function CreateSolver: ISolver; override;
published published
procedure TestPart1; procedure TestPart1;
procedure TestPart2;
end; end;
{ THotSpringsTestCase } { THotSpringsTestCase }
@ -40,18 +51,29 @@ 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
procedure THotSpringsFullDataTestCase.TestPart2;
begin
AssertEquals(-1, FSolver.GetResultPart2);
end;
{ THotSpringsExampleTestCase } { THotSpringsExampleTestCase }
function THotSpringsExampleTestCase.CreateSolver: ISolver; function THotSpringsExampleTestCase.CreateSolver: ISolver;
@ -64,6 +86,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 +98,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