From 16e7528b347335efd77542408aaf35d679cc6b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 19 Nov 2024 18:14:14 +0100 Subject: [PATCH] Updated day 12 WIP performance refactor - Added TValidationsToBlockAssignments to replace the loops in TConditionRecord.GenerateBlockAssignments --- UMultiIndexEnumerator.pas | 1 + solvers/UHotSprings.pas | 538 +++++++++++++++++++------------------- 2 files changed, 265 insertions(+), 274 deletions(-) diff --git a/UMultiIndexEnumerator.pas b/UMultiIndexEnumerator.pas index ca02c55..67dc020 100644 --- a/UMultiIndexEnumerator.pas +++ b/UMultiIndexEnumerator.pas @@ -52,6 +52,7 @@ type 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; diff --git a/solvers/UHotSprings.pas b/solvers/UHotSprings.pas index a7b47b6..5035ec0 100644 --- a/solvers/UHotSprings.pas +++ b/solvers/UHotSprings.pas @@ -35,8 +35,6 @@ const type TValidationLengths = array of array of Integer; - // TODO: TIntegerArray probably not needed. - TIntegerArray = array of Integer; { TDamage } @@ -63,20 +61,57 @@ type 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 - FValidation: TIntegerList; - FValidationLengths: TValidationLengths; - FDamages: TDamages; + FConditionRecord: TConditionRecord; + FBlock: TBlock; FValidationStartIndex, FValidationStopIndex: Integer; - // Calculates "span", the length of all damages for this validation number combined. + // 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 AValidation: TIntegerList; constref AValidationLengths: TValidationLengths; - constref ADamages: TDamages; const AStartIndex, AStopIndex: Integer); + 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; @@ -92,34 +127,21 @@ type TValidationPositionInfos = specialize TList; - TConditionRecord = class; - - { TAccumulatedCombinationsMultiIndexStrategy } - - 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; - { 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, FStartIndex, FStopIndex: Integer; + 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, AStartIndex, AStopIndex: Integer); + const ABlockLength, AValidationStartIndex, AValidationStopIndex: Integer); function GetCardinality: Integer; override; function TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer; out AStartIndexValue: Integer): Boolean; override; @@ -131,6 +153,7 @@ type 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; @@ -140,11 +163,9 @@ type 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; + FMinIndices: TIndexArray; procedure InitValidationLengths; procedure InitMinIndices; - function CalcCombinations(constref AIndices: TIntegerArray): Int64; - function CalcCombinationsBlock(constref ABlock: TBlock; const AStartIndex, AStopIndex: Integer): Int64; function CalcCombinationsBlockSingleValidation(constref ABlock: TBlock; const AIndex: Integer): Int64; function CalcCombinationsBlockMultiValidations(constref ABlock: TBlock; constref AIndices: TIndexArray; const AStartIndex, AStopIndex: Integer): Int64; @@ -154,8 +175,12 @@ type // 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 } @@ -219,6 +244,104 @@ begin 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; @@ -229,24 +352,23 @@ begin spanStart := ALastDamageIndex; while (spanStart > 0) and (ACurrentIndexArray[spanStart - 1] = AValidationNumber) do Dec(spanStart); - Result := FDamages[ALastDamageIndex].Length; + Result := FBlock.Damages[ALastDamageIndex].Length; if spanStart < ALastDamageIndex then - Inc(Result, FDamages[ALastDamageIndex].Start - FDamages[spanStart].Start); + Inc(Result, FBlock.Damages[ALastDamageIndex].Start - FBlock.Damages[spanStart].Start); end; -constructor TDamageToValidationAssignments.Create(constref AValidation: TIntegerList; constref AValidationLengths: - TValidationLengths; constref ADamages: TDamages; const AStartIndex, AStopIndex: Integer); +constructor TDamageToValidationAssignments.Create(constref AConditionRecord: TConditionRecord; constref ABlock: TBlock; + const AStartValidationIndex, AStopValidationIndex: Integer); begin - FValidation := AValidation; - FValidationLengths := AValidationLengths; - FDamages := ADamages; - FValidationStartIndex := AStartIndex; - FValidationStopIndex := AStopIndex; + FConditionRecord := AConditionRecord; + FBlock := ABlock; + FValidationStartIndex := AStartValidationIndex; + FValidationStopIndex := AStopValidationIndex; end; function TDamageToValidationAssignments.GetCardinality: Integer; begin - Result := FDamages.Count; + Result := FBlock.Damages.Count; end; function TDamageToValidationAssignments.TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; @@ -262,78 +384,32 @@ end; function TDamageToValidationAssignments.ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): TIndexValidationResult; var - i, prev, firstSkip: Integer; + i, prev: Integer; begin i := ACurrentIndexArray[ACurrentIndex]; + prev := ACurrentIndex - 1; + // Checks maximum index value. if i > FValidationStopIndex then - begin - Result := ivrBacktrack; - Exit; - end; - + Result := ivrBacktrack // 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; - + 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. - if (FValidationStartIndex < i) - and (FValidationLengths[FValidationStartIndex, i] + 1 >= FDamages[ACurrentIndex].Start) then - begin - Result := ivrBacktrack; - Exit; - end; - + 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. - 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; - + 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. - if FValidation[i] < CalcValidationSpan(ACurrentIndexArray, ACurrentIndex, i) then - begin - Result := ivrSkip; - Exit; - end; - - Result := ivrValid; -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 := ivrBacktrack - 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 - Result := FAccumulatedCombinations[GetCardinality - 1]; + else if FConditionRecord.Validation[i] < CalcValidationSpan(ACurrentIndexArray, ACurrentIndex, i) then + Result := ivrSkip + else + Result := ivrValid; end; { TValidationPositionOffsets } @@ -354,25 +430,26 @@ begin else begin // Handles first calculated offset. space := ACurrentIndexArray[0] - 2; - Result := FConditionRecord.CalcCombinationsWildcardSequence(space, FStartIndex, stop); + 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]; - Result := Result * FConditionRecord.CalcCombinationsWildcardSequence(space, FPositionInfos.Last.ValidationIndex + 1, FStopIndex); + 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, AStartIndex, AStopIndex: Integer); + TValidationPositionInfos; const ABlockLength, AValidationStartIndex, AValidationStopIndex: Integer); begin FConditionRecord := AConditionRecord; FPositionInfos := APositionInfos; FBlockLength := ABlockLength; - FStartIndex := AStartIndex; - FStopIndex := AStopIndex; + FValidationStartIndex := AValidationStartIndex; + FValidationStopIndex := AValidationStopIndex; inherited Create; end; @@ -387,7 +464,6 @@ var info: TValidationPositionInfo; begin info := FPositionInfos[ACurrentIndex]; - // Calculates start value such that the validation number just includes MinEnd. AStartIndexValue := info.MinStart; // Adjusts start value to avoid overlap of this validation number with the previous one (the one from previous // position info). @@ -442,122 +518,6 @@ begin 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 FBlocks[i].Damages.Count > 0 then - r := CalcCombinationsBlock(FBlocks[i], AIndices[i], AIndices[i + 1] - 1) - else begin - {$ifdef debug} - Write(' ', FBlocks[i].Pattern, ' '); - for j := AIndices[i] to AIndices[i + 1] - 1 do - Write(FValidation[j], ' '); - WriteLn; - Write(' count/space/freedoms: '); - {$endif} - r := CalcCombinationsWildcardSequence(Length(FBlocks[i].Pattern), 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(constref ABlock: TBlock; const AStartIndex, AStopIndex: Integer): Int64; -var - i, j, k: Integer; - indices: TIndexArray; - validationToDamageAssignments: TDamageToValidationAssignments; -begin - {$ifdef debug} - Write(' ', ABlock.Pattern, ' '); - for i := AStartIndex to AStopIndex do - Write(FValidation[i], ' '); - WriteLn; - {$endif} - - // 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. - 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 ABlock.Damages.Count - 1 do - begin - WriteLn(' damage: start ',ABlock.Damages[i].Start, ', length ', ABlock.Damages[i].Length, ', remain ', ABlock.Damages[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] < ABlock.Damages[i].Start) - // Enough space after damage for the other validation numbers? - and (FValidationLengths[j, AStopIndex + 1] - FValidation[j] <= ABlock.Damages[i].CharsRemaining) - // Damage itself small enough for this validation number? - and (FValidation[j] >= ABlock.Damages[i].Length) then - Write(j - AStartIndex, ' '); - WriteLn; - end; - {$endif} - - Result := 0; - - // Assigns validation numbers to specific damages. - validationToDamageAssignments := TDamageToValidationAssignments.Create(FValidation, FValidationLengths, ABlock.Damages, - AStartIndex, AStopIndex); - {$ifdef debug} - WriteLn(' validation numbers (indices) per damages:'); - {$endif} - for indices in validationToDamageAssignments do - begin - {$ifdef debug} - Write(' '); - for i := 0 to ABlock.Damages.Count - 1 do - Write(FValidation[indices[i]], ' '); - Write('( '); - for i := 0 to ABlock.Damages.Count - 1 do - Write(indices[i] - AStartIndex, ' '); - WriteLn(')'); - {$endif} - Result := Result + CalcCombinationsBlockMultiValidations(ABlock, indices, AStartIndex, AStopIndex); - end; - validationToDamageAssignments.Free; - end; -end; - function TConditionRecord.CalcCombinationsBlockSingleValidation(constref ABlock: TBlock; const AIndex: Integer): Int64; var len, combinedDamagesLength: Integer; @@ -658,63 +618,93 @@ 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; + validationsToBlockAssignments: TValidationsToBlockAssignments; + indices: TIndexArray; 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 + 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 + i, j, k: Integer; + indices: TIndexArray; + damageToValidationAssignments: TDamageToValidationAssignments; +begin + {$ifdef debug} + Write(' ', ABlock.Pattern, ' '); + for i := AStartIndex to AStopIndex do + Write(FValidation[i], ' '); + WriteLn; + {$endif} + + // 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. + 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 ABlock.Damages.Count - 1 do begin - indices[i] := Max(indices[i - 1], FMinIndices[i - 1]); - while FValidationLengths[indices[i - 1], indices[i]] > Length(FBlocks[i - 1].Pattern) do - begin - Dec(i); - Inc(indices[i]); - end; - - Inc(i); + WriteLn(' damage: start ',ABlock.Damages[i].Start, ', length ', ABlock.Damages[i].Length, ', remain ', ABlock.Damages[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] < ABlock.Damages[i].Start) + // Enough space after damage for the other validation numbers? + and (FValidationLengths[j, AStopIndex + 1] - FValidation[j] <= ABlock.Damages[i].CharsRemaining) + // Damage itself small enough for this validation number? + and (FValidation[j] >= ABlock.Damages[i].Length) then + Write(j - AStartIndex, ' '); + WriteLn; end; + {$endif} - Inc(count); - r := CalcCombinations(indices); - if r = 0 then - Inc(misses); - Result := Result + r; + Result := 0; - k := high; - while (k > 0) - and ((indices[k] = FValidation.Count) - or (FValidationLengths[indices[k - 1], indices[k] + 1] > Length(FBlocks[k - 1].Pattern))) do - Dec(k); - Inc(indices[k]); - until k = 0; - WriteLn(' missed: ', misses, '/', count); + // Assigns validation numbers to specific damages. + damageToValidationAssignments := TDamageToValidationAssignments.Create(Self, ABlock, AStartIndex, AStopIndex); + {$ifdef debug} + WriteLn(' validation numbers (indices) per damages:'); + {$endif} + for indices in damageToValidationAssignments do + begin + {$ifdef debug} + Write(' '); + for i := 0 to ABlock.Damages.Count - 1 do + Write(FValidation[indices[i]], ' '); + Write('( '); + for i := 0 to ABlock.Damages.Count - 1 do + Write(indices[i] - AStartIndex, ' '); + WriteLn(')'); + {$endif} + Result := Result + CalcCombinationsBlockMultiValidations(ABlock, indices, AStartIndex, AStopIndex); + end; + damageToValidationAssignments.Free; + end; end; function TConditionRecord.CalcCombinationsWildcardSequence(const ASequenceLength, AStartIndex, AStopIndex: Integer):