Added TBlock to replace the block strings for day 12

This commit is contained in:
Stefan Müller 2024-11-15 19:08:33 +01:00
parent 1642c7dcfb
commit 05863842df
1 changed files with 114 additions and 97 deletions

View File

@ -43,10 +43,25 @@ type
TDamage = record TDamage = record
Start, Length, CharsRemaining: Integer; Start, Length, CharsRemaining: Integer;
end; end;
TDamages = specialize TList<TDamage>; 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>; { TBlock }
TBlock = class
private
FPattern: string;
FDamages: TDamages;
procedure ParseDamages;
public
constructor Create(const APattern: string);
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;
end;
TBlocks = specialize TObjectList<TBlock>;
{ TValidationToDamageAssignments } { TValidationToDamageAssignments }
@ -99,7 +114,7 @@ type
FBinomialCoefficients: TBinomialCoefficientCache; FBinomialCoefficients: TBinomialCoefficientCache;
FValidation: TIntegerList; FValidation: TIntegerList;
// List of non-empty, maximum-length parts of the pattern without operational springs ("blocks"). // List of non-empty, maximum-length parts of the pattern without operational springs ("blocks").
FBlocks: TStringList; FBlocks: TBlocks;
// Array 'a' of accumulated validation series lengths. 'a[i, j]' denotes the combined length of consecutive // 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 // validation numbers from 'FValidation[i]' to 'FValidation[j - 1]' with a single space in between each pair of
// them. // them.
@ -107,32 +122,25 @@ type
// Array 'a' of minimum indices 'a[i]', such that all remaining validation numbers starting at index 'a[i] - 1' // 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]'. // cannot fit into the remaining blocks starting at 'FBlocks[i]'.
FMinIndices: TIntegerArray; 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 InitValidationLengths;
procedure InitMinIndices; procedure InitMinIndices;
function CalcCombinations(constref AIndices: TIntegerArray): Int64; function CalcCombinations(constref AIndices: TIntegerArray): Int64;
function CalcCombinationsBlock(const ABlock: string; constref ADamages: TDamages; const AStartIndex, AStopIndex: function CalcCombinationsBlock(constref ABlock: TBlock; const AStartIndex, AStopIndex: Integer): Int64;
Integer): Int64; function CalcCombinationsBlockSingleValidation(constref ABlock: TBlock; const AIndex: Integer): Int64;
function CalcCombinationsBlockSingleValidation(const ABlockLength: Integer; constref ADamages: TDamages; function CalcCombinationsBlockMultiValidations(constref ABlock: TBlock; constref AIndices: TIndexArray;
const AIndex: Integer): Int64; const AStartIndex, AStopIndex: 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: function CalcCombinationsBlockAssignedValidations(const ABlockLength: Integer; constref APositionInfos:
TValidationPositionInfos; constref AOffsets: TIndexArray; const AStartIndex, AStopIndex: Integer): Int64; TValidationPositionInfos; constref AOffsets: TIndexArray; const AStartIndex, AStopIndex: Integer): Int64;
function CalcCombinationsWildcardSequence(const ASequenceLength, AStartIndex, AStopIndex: Integer): Int64; function CalcCombinationsWildcardSequence(const ASequenceLength, AStartIndex, AStopIndex: Integer): Int64;
function ParseDamages(const ABlock: string): TDamages;
public public
property Blocks: TStringList read FBlocks;
property Validation: TIntegerList read FValidation;
constructor Create(constref ABinomialCoefficients: TBinomialCoefficientCache); constructor Create(constref ABinomialCoefficients: TBinomialCoefficientCache);
destructor Destroy; override; destructor Destroy; override;
// Adds all non-empty, maximum-length parts of the pattern without operational springs ("blocks"). // Adds all non-empty, maximum-length parts of the pattern without operational springs ("blocks").
procedure AddBlocks(const APattern: string); procedure AddBlocks(const APattern: string);
function GenerateBlockAssignments: Int64; function GenerateBlockAssignments: Int64;
// TODO: Blocks is not needed?
//property Blocks: TBlocks read FBlocks;
property Validation: TIntegerList read FValidation;
end; end;
{ THotSprings } { THotSprings }
@ -154,6 +162,50 @@ type
implementation 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);
begin
FPattern := APattern;
ParseDamages;
end;
destructor TBlock.Destroy;
begin
FDamages.Free;
inherited Destroy;
end;
{ TValidationToDamageAssignments } { TValidationToDamageAssignments }
function TValidationToDamageAssignments.CalcValidationSpan(constref ACurrentIndexArray: TIndexArray; function TValidationToDamageAssignments.CalcValidationSpan(constref ACurrentIndexArray: TIndexArray;
@ -307,14 +359,14 @@ var
i, j, patternsLength: Integer; i, j, patternsLength: Integer;
begin begin
SetLength(FMinIndices, FBlocks.Count - 1); SetLength(FMinIndices, FBlocks.Count - 1);
patternsLength := Length(FBlocks[FBlocks.Count - 1]); patternsLength := Length(FBlocks[FBlocks.Count - 1].Pattern);
j := FValidation.Count; j := FValidation.Count;
for i := FBlocks.Count - 2 downto 0 do for i := FBlocks.Count - 2 downto 0 do
begin begin
while (j >= 0) and (FValidationLengths[j, FValidation.Count] <= patternsLength) do while (j >= 0) and (FValidationLengths[j, FValidation.Count] <= patternsLength) do
Dec(j); Dec(j);
FMinIndices[i] := j + 1; FMinIndices[i] := j + 1;
patternsLength := patternsLength + 1 + Length(FBlocks[i]); patternsLength := patternsLength + 1 + Length(FBlocks[i].Pattern);
end; end;
end; end;
@ -334,17 +386,17 @@ begin
i := 0; i := 0;
while (Result > 0) and (i < FBlocks.Count) do while (Result > 0) and (i < FBlocks.Count) do
begin begin
if FDamagesBlocks[i].Count > 0 then if FBlocks[i].Damages.Count > 0 then
r := CalcCombinationsBlock(FBlocks[i], FDamagesBlocks[i], AIndices[i], AIndices[i + 1] - 1) r := CalcCombinationsBlock(FBlocks[i], AIndices[i], AIndices[i + 1] - 1)
else begin else begin
{$ifdef debug} {$ifdef debug}
Write(' ', FBlocks[i], ' '); Write(' ', FBlocks[i].Pattern, ' ');
for j := AIndices[i] to AIndices[i + 1] - 1 do for j := AIndices[i] to AIndices[i + 1] - 1 do
Write(FValidation[j], ' '); Write(FValidation[j], ' ');
WriteLn; WriteLn;
Write(' count/space/freedoms: '); Write(' count/space/freedoms: ');
{$endif} {$endif}
r := CalcCombinationsWildcardSequence(Length(FBlocks[i]), AIndices[i], AIndices[i + 1] - 1); r := CalcCombinationsWildcardSequence(Length(FBlocks[i].Pattern), AIndices[i], AIndices[i + 1] - 1);
{$ifdef debug} {$ifdef debug}
WriteLn(' result: ', r); WriteLn(' result: ', r);
{$endif} {$endif}
@ -357,15 +409,14 @@ begin
end; end;
end; end;
function TConditionRecord.CalcCombinationsBlock(const ABlock: string; constref ADamages: TDamages; const AStartIndex, function TConditionRecord.CalcCombinationsBlock(constref ABlock: TBlock; const AStartIndex, AStopIndex: Integer): Int64;
AStopIndex: Integer): Int64;
var var
i, j, k: Integer; i, j, k: Integer;
indices: TIndexArray; indices: TIndexArray;
validationToDamageAssignments: TValidationToDamageAssignments; validationToDamageAssignments: TValidationToDamageAssignments;
begin begin
{$ifdef debug} {$ifdef debug}
Write(' ', ABlock, ' '); Write(' ', ABlock.Pattern, ' ');
for i := AStartIndex to AStopIndex do for i := AStartIndex to AStopIndex do
Write(FValidation[i], ' '); Write(FValidation[i], ' ');
WriteLn; WriteLn;
@ -374,14 +425,14 @@ begin
// No validation number assigned to this block. // No validation number assigned to this block.
if AStartIndex > AStopIndex then if AStartIndex > AStopIndex then
begin begin
if ADamages.Count = 0 then if ABlock.Damages.Count = 0 then
Result := 1 Result := 1
else else
Result := 0; Result := 0;
end end
// One validation number assigned to this block. // One validation number assigned to this block.
else if AStartIndex = AStopIndex then else if AStartIndex = AStopIndex then
Result := CalcCombinationsBlockSingleValidation(Length(ABlock), ADamages, AStartIndex) Result := CalcCombinationsBlockSingleValidation(ABlock, AStartIndex)
// Multiple validation numbers assigned to this block. // Multiple validation numbers assigned to this block.
else begin else begin
{$ifdef debug} {$ifdef debug}
@ -394,17 +445,17 @@ begin
Write(FValidationLengths[i, AStopIndex + 1] - FValidation[i], ' '); Write(FValidationLengths[i, AStopIndex + 1] - FValidation[i], ' ');
WriteLn; WriteLn;
for i := 0 to ADamages.Count - 1 do for i := 0 to ABlock.Damages.Count - 1 do
begin begin
WriteLn(' damage: start ',ADamages[i].Start, ', length ', ADamages[i].Length, ', remain ', ADamages[i].CharsRemaining); WriteLn(' damage: start ',ABlock.Damages[i].Start, ', length ', ABlock.Damages[i].Length, ', remain ', ABlock.Damages[i].CharsRemaining);
Write(' '); Write(' ');
for j := AStartIndex to AStopIndex do for j := AStartIndex to AStopIndex do
// Enough space before damage for the other validation numbers? // Enough space before damage for the other validation numbers?
if (FValidationLengths[AStartIndex, j + 1] - FValidation[j] < ADamages[i].Start) if (FValidationLengths[AStartIndex, j + 1] - FValidation[j] < ABlock.Damages[i].Start)
// Enough space after damage for the other validation numbers? // Enough space after damage for the other validation numbers?
and (FValidationLengths[j, AStopIndex + 1] - FValidation[j] <= ADamages[i].CharsRemaining) and (FValidationLengths[j, AStopIndex + 1] - FValidation[j] <= ABlock.Damages[i].CharsRemaining)
// Damage itself small enough for this validation number? // Damage itself small enough for this validation number?
and (FValidation[j] >= ADamages[i].Length) then and (FValidation[j] >= ABlock.Damages[i].Length) then
Write(j - AStartIndex, ' '); Write(j - AStartIndex, ' ');
WriteLn; WriteLn;
end; end;
@ -413,7 +464,7 @@ begin
Result := 0; Result := 0;
// Assigns validation numbers to specific damages. // Assigns validation numbers to specific damages.
validationToDamageAssignments := TValidationToDamageAssignments.Create(FValidation, FValidationLengths, ADamages, validationToDamageAssignments := TValidationToDamageAssignments.Create(FValidation, FValidationLengths, ABlock.Damages,
AStartIndex, AStopIndex); AStartIndex, AStopIndex);
{$ifdef debug} {$ifdef debug}
WriteLn(' validation numbers (indices) per damages:'); WriteLn(' validation numbers (indices) per damages:');
@ -422,44 +473,44 @@ begin
begin begin
{$ifdef debug} {$ifdef debug}
Write(' '); Write(' ');
for i := 0 to ADamages.Count - 1 do for i := 0 to ABlock.Damages.Count - 1 do
Write(FValidation[indices[i]], ' '); Write(FValidation[indices[i]], ' ');
Write('( '); Write('( ');
for i := 0 to ADamages.Count - 1 do for i := 0 to ABlock.Damages.Count - 1 do
Write(indices[i] - AStartIndex, ' '); Write(indices[i] - AStartIndex, ' ');
WriteLn(')'); WriteLn(')');
{$endif} {$endif}
Result := Result + CalcCombinationsBlockMultiValidations(Length(ABlock), ADamages, indices, AStartIndex, AStopIndex); Result := Result + CalcCombinationsBlockMultiValidations(ABlock, indices, AStartIndex, AStopIndex);
end; end;
validationToDamageAssignments.Free; validationToDamageAssignments.Free;
end; end;
end; end;
function TConditionRecord.CalcCombinationsBlockSingleValidation(const ABlockLength: Integer; constref ADamages: function TConditionRecord.CalcCombinationsBlockSingleValidation(constref ABlock: TBlock; const AIndex: Integer): Int64;
TDamages; const AIndex: Integer): Int64;
var var
combinedDamagesLength: Integer; len, combinedDamagesLength: Integer;
begin begin
if ABlockLength < FValidation[AIndex] then len := Length(ABlock.Pattern);
if len < FValidation[AIndex] then
Result := 0 Result := 0
else if ADamages.Count = 0 then else if ABlock.Damages.Count = 0 then
Result := ABlockLength - FValidation[AIndex] + 1 Result := len - FValidation[AIndex] + 1
else begin else begin
combinedDamagesLength := ADamages.Last.Start + ADamages.Last.Length - ADamages.First.Start; combinedDamagesLength := ABlock.Damages.Last.Start + ABlock.Damages.Last.Length - ABlock.Damages.First.Start;
if FValidation[AIndex] < combinedDamagesLength then if FValidation[AIndex] < combinedDamagesLength then
Result := 0 Result := 0
else begin else begin
Result := Min(Min(Min( Result := Min(Min(Min(
ADamages.First.Start, ABlock.Damages.First.Start,
FValidation[AIndex] - combinedDamagesLength + 1), FValidation[AIndex] - combinedDamagesLength + 1),
ABlockLength - FValidation[AIndex] + 1), len - FValidation[AIndex] + 1),
ADamages.Last.CharsRemaining + 1); ABlock.Damages.Last.CharsRemaining + 1);
end; end;
end; end;
end; end;
function TConditionRecord.CalcCombinationsBlockMultiValidations(const ABlockLength: Integer; constref ADamages: function TConditionRecord.CalcCombinationsBlockMultiValidations(constref ABlock: TBlock; constref AIndices:
TDamages; constref AIndices: TIndexArray; const AStartIndex, AStopIndex: Integer): Int64; TIndexArray; const AStartIndex, AStopIndex: Integer): Int64;
var var
i, high: Integer; i, high: Integer;
position: TValidationPositionInfo; position: TValidationPositionInfo;
@ -471,25 +522,25 @@ begin
high := Length(AIndices) - 1; high := Length(AIndices) - 1;
// Initializes first info record. // Initializes first info record.
position.ValidationIndex := AIndices[0]; position.ValidationIndex := AIndices[0];
position.MaxStart := ADamages[0].Start; position.MaxStart := ABlock.Damages[0].Start;
position.MinStart := 1; position.MinStart := 1;
for i := 1 to high do for i := 1 to high do
if AIndices[i] <> position.ValidationIndex then if AIndices[i] <> position.ValidationIndex then
begin begin
// Finalizes current info record. // Finalizes current info record.
position.MaxStart := Min(position.MaxStart, ADamages[i].Start - 1 - FValidation[position.ValidationIndex]); position.MaxStart := Min(position.MaxStart, ABlock.Damages[i].Start - 1 - FValidation[position.ValidationIndex]);
position.MinStart := Max(position.MinStart, position.MinStart := Max(position.MinStart,
ADamages[i - 1].Start + ADamages[i - 1].Length - FValidation[position.ValidationIndex]); ABlock.Damages[i - 1].Start + ABlock.Damages[i - 1].Length - FValidation[position.ValidationIndex]);
positions.Add(position); positions.Add(position);
// Initializes next info record. // Initializes next info record.
position.ValidationIndex := AIndices[i]; position.ValidationIndex := AIndices[i];
position.MaxStart := ADamages[i].Start; position.MaxStart := ABlock.Damages[i].Start;
position.MinStart := position.MinStart + FValidationLengths[AIndices[i - 1], AIndices[i]] + 1; //FValidation[position.ValidationIndex - 1] + 1; position.MinStart := position.MinStart + FValidationLengths[AIndices[i - 1], AIndices[i]] + 1;
end; end;
// Finalizes last info record. // Finalizes last info record.
position.MaxStart := Min(position.MaxStart, ABlockLength + 1 - FValidation[position.ValidationIndex]); position.MaxStart := Min(position.MaxStart, Length(ABlock.Pattern) + 1 - FValidation[position.ValidationIndex]);
position.MinStart := Max(position.MinStart, position.MinStart := Max(position.MinStart,
ADamages[high].Start + ADamages[high].Length - FValidation[position.ValidationIndex]); ABlock.Damages[high].Start + ABlock.Damages[high].Length - FValidation[position.ValidationIndex]);
positions.Add(position); positions.Add(position);
{$ifdef debug} {$ifdef debug}
@ -502,7 +553,8 @@ begin
Result := 0; Result := 0;
validationPositionOffsets := TValidationPositionOffsets.Create(FValidation, positions); validationPositionOffsets := TValidationPositionOffsets.Create(FValidation, positions);
for offsets in validationPositionOffsets do for offsets in validationPositionOffsets do
Result := Result + CalcCombinationsBlockAssignedValidations(ABlockLength, positions, offsets, AStartIndex, AStopIndex); Result := Result
+ CalcCombinationsBlockAssignedValidations(Length(ABlock.Pattern), positions, offsets, AStartIndex, AStopIndex);
validationPositionOffsets.Free; validationPositionOffsets.Free;
positions.Free; positions.Free;
@ -571,50 +623,18 @@ begin
end; end;
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); constructor TConditionRecord.Create(constref ABinomialCoefficients: TBinomialCoefficientCache);
begin begin
FBinomialCoefficients := ABinomialCoefficients; FBinomialCoefficients := ABinomialCoefficients;
FBlocks := TStringList.Create; FBlocks := TBlocks.Create;
FValidation := TIntegerList.Create; FValidation := TIntegerList.Create;
FDamagesBlocks := TDamagesBlocks.Create;
end; end;
destructor TConditionRecord.Destroy; destructor TConditionRecord.Destroy;
begin begin
FBlocks.Free; FBlocks.Free;
FValidation.Free; FValidation.Free;
FDamagesBlocks.Free;
inherited Destroy; inherited Destroy;
end; end;
@ -626,10 +646,7 @@ begin
split := APattern.Split([COperationalChar]); split := APattern.Split([COperationalChar]);
for part in split do for part in split do
if Length(part) > 0 then if Length(part) > 0 then
begin FBlocks.Add(TBlock.Create(part));
FBlocks.Add(part);
FDamagesBlocks.Add(ParseDamages(part));
end;
end; end;
function TConditionRecord.GenerateBlockAssignments: Int64; function TConditionRecord.GenerateBlockAssignments: Int64;
@ -668,7 +685,7 @@ begin
while i <= high do while i <= high do
begin begin
indices[i] := Max(indices[i - 1], FMinIndices[i - 1]); indices[i] := Max(indices[i - 1], FMinIndices[i - 1]);
while FValidationLengths[indices[i - 1], indices[i]] > Length(FBlocks[i - 1]) do while FValidationLengths[indices[i - 1], indices[i]] > Length(FBlocks[i - 1].Pattern) do
begin begin
Dec(i); Dec(i);
Inc(indices[i]); Inc(indices[i]);
@ -686,7 +703,7 @@ begin
k := high; k := high;
while (k > 0) while (k > 0)
and ((indices[k] = FValidation.Count) and ((indices[k] = FValidation.Count)
or (FValidationLengths[indices[k - 1], indices[k] + 1] > Length(FBlocks[k - 1]))) do or (FValidationLengths[indices[k - 1], indices[k] + 1] > Length(FBlocks[k - 1].Pattern))) do
Dec(k); Dec(k);
Inc(indices[k]); Inc(indices[k]);
until k = 0; until k = 0;