diff --git a/solvers/UHotSprings.pas b/solvers/UHotSprings.pas index 8dbfd56..c82c383 100644 --- a/solvers/UHotSprings.pas +++ b/solvers/UHotSprings.pas @@ -43,10 +43,25 @@ type TDamage = record Start, Length, CharsRemaining: Integer; end; - TDamages = specialize TList; - // TODO: Instead of using TDamagesBlocks, "block" should be a record of a string and its associated list TDamages. - TDamagesBlocks = specialize TObjectList; + + { 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; { TValidationToDamageAssignments } @@ -99,7 +114,7 @@ type FBinomialCoefficients: TBinomialCoefficientCache; FValidation: TIntegerList; // 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 // validation numbers from 'FValidation[i]' to 'FValidation[j - 1]' with a single space in between each pair of // 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' // 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 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; 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; + // TODO: Blocks is not needed? + //property Blocks: TBlocks read FBlocks; + property Validation: TIntegerList read FValidation; end; { THotSprings } @@ -154,6 +162,50 @@ type 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 } function TValidationToDamageAssignments.CalcValidationSpan(constref ACurrentIndexArray: TIndexArray; @@ -307,14 +359,14 @@ var i, j, patternsLength: Integer; begin SetLength(FMinIndices, FBlocks.Count - 1); - patternsLength := Length(FBlocks[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]); + patternsLength := patternsLength + 1 + Length(FBlocks[i].Pattern); end; end; @@ -334,17 +386,17 @@ begin 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) + if FBlocks[i].Damages.Count > 0 then + r := CalcCombinationsBlock(FBlocks[i], AIndices[i], AIndices[i + 1] - 1) else begin {$ifdef debug} - Write(' ', FBlocks[i], ' '); + 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]), AIndices[i], AIndices[i + 1] - 1); + r := CalcCombinationsWildcardSequence(Length(FBlocks[i].Pattern), AIndices[i], AIndices[i + 1] - 1); {$ifdef debug} WriteLn(' result: ', r); {$endif} @@ -357,15 +409,14 @@ begin end; end; -function TConditionRecord.CalcCombinationsBlock(const ABlock: string; constref ADamages: TDamages; const AStartIndex, - AStopIndex: Integer): Int64; +function TConditionRecord.CalcCombinationsBlock(constref ABlock: TBlock; const AStartIndex, AStopIndex: Integer): Int64; var i, j, k: Integer; indices: TIndexArray; validationToDamageAssignments: TValidationToDamageAssignments; begin {$ifdef debug} - Write(' ', ABlock, ' '); + Write(' ', ABlock.Pattern, ' '); for i := AStartIndex to AStopIndex do Write(FValidation[i], ' '); WriteLn; @@ -374,14 +425,14 @@ begin // No validation number assigned to this block. if AStartIndex > AStopIndex then begin - if ADamages.Count = 0 then + 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(Length(ABlock), ADamages, AStartIndex) + Result := CalcCombinationsBlockSingleValidation(ABlock, AStartIndex) // Multiple validation numbers assigned to this block. else begin {$ifdef debug} @@ -394,17 +445,17 @@ begin Write(FValidationLengths[i, AStopIndex + 1] - FValidation[i], ' '); WriteLn; - for i := 0 to ADamages.Count - 1 do + for i := 0 to ABlock.Damages.Count - 1 do 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(' '); 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) + 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] <= ADamages[i].CharsRemaining) + and (FValidationLengths[j, AStopIndex + 1] - FValidation[j] <= ABlock.Damages[i].CharsRemaining) // 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, ' '); WriteLn; end; @@ -413,7 +464,7 @@ begin Result := 0; // Assigns validation numbers to specific damages. - validationToDamageAssignments := TValidationToDamageAssignments.Create(FValidation, FValidationLengths, ADamages, + validationToDamageAssignments := TValidationToDamageAssignments.Create(FValidation, FValidationLengths, ABlock.Damages, AStartIndex, AStopIndex); {$ifdef debug} WriteLn(' validation numbers (indices) per damages:'); @@ -422,44 +473,44 @@ begin begin {$ifdef debug} Write(' '); - for i := 0 to ADamages.Count - 1 do + for i := 0 to ABlock.Damages.Count - 1 do Write(FValidation[indices[i]], ' '); Write('( '); - for i := 0 to ADamages.Count - 1 do + for i := 0 to ABlock.Damages.Count - 1 do Write(indices[i] - AStartIndex, ' '); WriteLn(')'); {$endif} - Result := Result + CalcCombinationsBlockMultiValidations(Length(ABlock), ADamages, indices, AStartIndex, AStopIndex); + Result := Result + CalcCombinationsBlockMultiValidations(ABlock, indices, AStartIndex, AStopIndex); end; validationToDamageAssignments.Free; end; end; -function TConditionRecord.CalcCombinationsBlockSingleValidation(const ABlockLength: Integer; constref ADamages: - TDamages; const AIndex: Integer): Int64; +function TConditionRecord.CalcCombinationsBlockSingleValidation(constref ABlock: TBlock; const AIndex: Integer): Int64; var - combinedDamagesLength: Integer; + len, combinedDamagesLength: Integer; begin - if ABlockLength < FValidation[AIndex] then + len := Length(ABlock.Pattern); + if len < FValidation[AIndex] then Result := 0 - else if ADamages.Count = 0 then - Result := ABlockLength - FValidation[AIndex] + 1 + else if ABlock.Damages.Count = 0 then + Result := len - FValidation[AIndex] + 1 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 Result := 0 else begin Result := Min(Min(Min( - ADamages.First.Start, + ABlock.Damages.First.Start, FValidation[AIndex] - combinedDamagesLength + 1), - ABlockLength - FValidation[AIndex] + 1), - ADamages.Last.CharsRemaining + 1); + len - FValidation[AIndex] + 1), + ABlock.Damages.Last.CharsRemaining + 1); end; end; end; -function TConditionRecord.CalcCombinationsBlockMultiValidations(const ABlockLength: Integer; constref ADamages: - TDamages; constref AIndices: TIndexArray; const AStartIndex, AStopIndex: Integer): Int64; +function TConditionRecord.CalcCombinationsBlockMultiValidations(constref ABlock: TBlock; constref AIndices: + TIndexArray; const AStartIndex, AStopIndex: Integer): Int64; var i, high: Integer; position: TValidationPositionInfo; @@ -471,25 +522,25 @@ begin high := Length(AIndices) - 1; // Initializes first info record. position.ValidationIndex := AIndices[0]; - position.MaxStart := ADamages[0].Start; + 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, 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, - 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); // 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; + 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, ABlockLength + 1 - FValidation[position.ValidationIndex]); + position.MaxStart := Min(position.MaxStart, Length(ABlock.Pattern) + 1 - FValidation[position.ValidationIndex]); 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); {$ifdef debug} @@ -502,7 +553,8 @@ begin Result := 0; validationPositionOffsets := TValidationPositionOffsets.Create(FValidation, positions); 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; positions.Free; @@ -571,50 +623,18 @@ begin 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; + FBlocks := TBlocks.Create; FValidation := TIntegerList.Create; - FDamagesBlocks := TDamagesBlocks.Create; end; destructor TConditionRecord.Destroy; begin FBlocks.Free; FValidation.Free; - FDamagesBlocks.Free; inherited Destroy; end; @@ -626,10 +646,7 @@ begin split := APattern.Split([COperationalChar]); for part in split do if Length(part) > 0 then - begin - FBlocks.Add(part); - FDamagesBlocks.Add(ParseDamages(part)); - end; + FBlocks.Add(TBlock.Create(part)); end; function TConditionRecord.GenerateBlockAssignments: Int64; @@ -668,7 +685,7 @@ begin 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 + while FValidationLengths[indices[i - 1], indices[i]] > Length(FBlocks[i - 1].Pattern) do begin Dec(i); Inc(indices[i]); @@ -686,7 +703,7 @@ begin k := high; while (k > 0) 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); Inc(indices[k]); until k = 0;