diff --git a/AdventOfCode.lpi b/AdventOfCode.lpi index 2a9af4b..74c9eb4 100644 --- a/AdventOfCode.lpi +++ b/AdventOfCode.lpi @@ -157,6 +157,14 @@ + + + + + + + + diff --git a/UBinomialCoefficients.pas b/UBinomialCoefficients.pas new file mode 100644 index 0000000..71c4780 --- /dev/null +++ b/UBinomialCoefficients.pas @@ -0,0 +1,105 @@ +{ + 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 . +} + +unit UBinomialCoefficients; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, Generics.Collections; + +type + TCardinalArray = array of Cardinal; + TCardinalArrays = specialize TList; + + { 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; + +var + BinomialCoefficients: TBinomialCoefficientCache; + +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; + +initialization + BinomialCoefficients := TBinomialCoefficientCache.Create; + +finalization + BinomialCoefficients.Free; + +end. + diff --git a/UCommon.pas b/UCommon.pas index bae26e7..b02c18e 100644 --- a/UCommon.pas +++ b/UCommon.pas @@ -40,6 +40,7 @@ const CPCardinalDirections: array[0..3] of PPoint = (@CDirectionRight, @CDirectionDown, @CDirectionLeft, @CDirectionUp); type + TInt64Array = array of Int64; TIntegerList = specialize TList; TPoints = specialize TList; diff --git a/UMultiIndexEnumerator.pas b/UMultiIndexEnumerator.pas new file mode 100644 index 0000000..67dc020 --- /dev/null +++ b/UMultiIndexEnumerator.pas @@ -0,0 +1,161 @@ +{ + 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 . +} + +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) + 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) + 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; + 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; +begin + Result := TMultiIndexEnumerator.Create(Self); +end; + +end. + diff --git a/solvers/UHotSprings.pas b/solvers/UHotSprings.pas index 8f12cab..0078c88 100644 --- a/solvers/UHotSprings.pas +++ b/solvers/UHotSprings.pas @@ -1,6 +1,6 @@ { 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 the terms of the GNU General Public License as published by the Free Software @@ -22,27 +22,176 @@ unit UHotSprings; interface uses - Classes, SysUtils, Generics.Collections, USolver; + Classes, SysUtils, Math, Generics.Collections, USolver, UCommon, UMultiIndexEnumerator, UBinomialCoefficients; const COperationalChar = '.'; CDamagedChar = '#'; CWildcardChar = '?'; - COperationalPatternChars = [COperationalChar, CWildcardChar]; - CDamagedPatternChars = [CDamagedChar, CWildcardChar]; + CPart2Repetition = 5; type + TValidationLengths = array of array of Integer; + + { TDamage } + + TDamage = record + Start, Length, CharsRemaining: Integer; + end; + TDamages = specialize TList; + + TBlockCombinationsCache = specialize THashMap; + TCombinationsCache = specialize TObjectHashMap; + + { TBlock } + + TBlock = class + private + FPattern: string; + FDamages: TDamages; + FCombinationsCache: TBlockCombinationsCache; + procedure ParseDamages; + public + constructor Create(const APattern: string; constref ACombinationsCache: TBlockCombinationsCache); + destructor Destroy; override; + property Pattern: string read FPattern; + // List of damages in this block, containing exactly one entry for each sequence of consecutive damage characters in + // the block's pattern, ordered such that a damage with lower index is further left. + // For example, if Pattern is '??##?#?', then Damages would have 2 entries. + property Damages: TDamages read FDamages; + property CombinationsCache: TBlockCombinationsCache read FCombinationsCache; + end; + TBlocks = specialize TObjectList; + + { TAccumulatedCombinationsMultiIndexStrategy } + + // Adds accumulated combinations to the enumerable strategy to allow calculation of combinations on the fly, and + // therefore early rejection of invalid multi-index configurations. + TAccumulatedCombinationsMultiIndexStrategy = class(TEnumerableMultiIndexStrategy) + private + FAccumulatedCombinations: TInt64Array; + protected + function CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): Int64; virtual; + abstract; + function UpdateCombinations(const AValidationResult: TIndexValidationResult; constref ACurrentIndexArray: + TIndexArray; const ACurrentIndex: Integer): TIndexValidationResult; + public + function GetCombinations: Int64; + end; + + TConditionRecord = class; + + { TValidationsToBlockAssignments } + + // Enumerable strategy that enumerates all valid assignments of ranges of validation numbers to individual blocks in + // the form of start and stop indices. + TValidationsToBlockAssignments = class(TAccumulatedCombinationsMultiIndexStrategy) + private + FConditionRecord: TConditionRecord; + protected + function CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): Int64; override; + public + constructor Create(constref AConditionRecord: TConditionRecord); + function GetCardinality: Integer; override; + function TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer; + out AStartIndexValue: Integer): Boolean; override; + function ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): + TIndexValidationResult; override; + end; + + { TDamageToValidationAssignments } + + // Enumerable strategy that enumerates all valid assignments of each damage in the block to a specific validation + // number from the validation numbers that have been assigned to the block, as indicated by start and stop indices. + TDamageToValidationAssignments = class(TEnumerableMultiIndexStrategy) + private + FConditionRecord: TConditionRecord; + FBlock: TBlock; + FValidationStartIndex, FValidationStopIndex: Integer; + // Calculates "span", the length of all damages for one validation number combined. + function CalcValidationSpan(constref ACurrentIndexArray: TIndexArray; const ALastDamageIndex, AValidationNumber: + Integer): Integer; + public + constructor Create(constref AConditionRecord: TConditionRecord; constref ABlock: TBlock; + const AStartValidationIndex, AStopValidationIndex: Integer); + function GetCardinality: Integer; override; + function TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer; + out AStartIndexValue: Integer): Boolean; override; + function ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): + TIndexValidationResult; override; + end; + + { TValidationPositionInfo } + + TValidationPositionInfo = record + ValidationIndex, MinStart, MaxStart: Integer; + end; + + TValidationPositionInfos = specialize TList; + + { TValidationPositionOffsets } + + // Enumerable strategy that enumerates all valid assignments of start positions (positions mean character indices in + // the block patterns) of validation numbers that have been assigned to damages in the current block, as indicated by + // provided TValidationPositionInfos. + TValidationPositionOffsets = class(TAccumulatedCombinationsMultiIndexStrategy) + private + FConditionRecord: TConditionRecord; + FPositionInfos: TValidationPositionInfos; + FBlockLength, FValidationStartIndex, FValidationStopIndex: Integer; + protected + function CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): Int64; override; + public + constructor Create(constref AConditionRecord: TConditionRecord; constref APositionInfos: TValidationPositionInfos; + const ABlockLength, AValidationStartIndex, AValidationStopIndex: Integer); + function GetCardinality: Integer; override; + function TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer; + out AStartIndexValue: Integer): Boolean; override; + function ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): + TIndexValidationResult; override; + end; + + { TConditionRecord } + + TConditionRecord = class + private + // List of validation numbers as stated in the problem input. + FValidation: TIntegerList; + // List of non-empty, maximum-length parts of the pattern without operational springs ("blocks"). + FBlocks: TBlocks; + // Array 'a' of accumulated validation series lengths. 'a[i, j]' denotes the combined length of consecutive + // validation numbers from 'FValidation[i]' to 'FValidation[j - 1]' with a single space in between each pair of + // them. + FValidationLengths: TValidationLengths; + // Array 'a' of minimum indices 'a[i]', such that all remaining validation numbers starting at index 'a[i] - 1' + // cannot fit into the remaining blocks starting at 'FBlocks[i]'. + FMinIndices: TIndexArray; + FCombinationsCache: TCombinationsCache; + procedure InitValidationLengths; + procedure InitMinIndices; + function CalcCombinationsBlockSingleValidation(constref ABlock: TBlock; const AIndex: Integer): Int64; + function CalcCombinationsBlockMultiValidations(constref ABlock: TBlock; constref AIndices: TIndexArray; + const AStartIndex, AStopIndex: Integer): Int64; + function CalcValidationsId(const AStartIndex, AStopIndex: Integer): Int64; + public + constructor Create(constref ACombinationsCache: TCombinationsCache); + destructor Destroy; override; + // Adds all non-empty, maximum-length parts of the pattern without operational springs ("blocks"). + procedure AddBlocks(const APattern: string); + function GenerateBlockAssignments: Int64; + function CalcCombinationsBlock(constref ABlock: TBlock; const AStartIndex, AStopIndex: Integer): Int64; + function CalcCombinationsWildcardSequence(const ASequenceLength, AStartIndex, AStopIndex: Integer): Int64; + property Validation: TIntegerList read FValidation; + property Blocks: TBlocks read FBlocks; + property ValidationLengths: TValidationLengths read FValidationLengths; + property MinIndices: TIndexArray read FMinIndices; + end; { THotSprings } THotSprings = class(TSolver) private - FValidation: specialize TList; - FSpringPattern: string; - procedure ExtendArrangement(const AArrangement: string; const ARemainingFreeOperationalCount, ACurrentValidationIndex: - Integer); - function TryAppendOperationalChar(var AArrangement: string): Boolean; - function TryAppendValidationBlock(var AArrangement: string; const ALength: Integer): Boolean; + FCombinationsCache: TCombinationsCache; public constructor Create; destructor Destroy; override; @@ -54,99 +203,549 @@ type implementation -{ THotSprings } +{ TBlock } -procedure THotSprings.ExtendArrangement(const AArrangement: string; const ARemainingFreeOperationalCount, - ACurrentValidationIndex: Integer); -var - match: Boolean; - temp: string; -begin - if Length(AArrangement) = Length(FSpringPattern) then - Inc(FPart1) - else begin - temp := AArrangement; - // Tries to append a dot (operational) to the current arrangement. - if (ARemainingFreeOperationalCount > 0) and TryAppendOperationalChar(temp) then - 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; - -function THotSprings.TryAppendOperationalChar(var AArrangement: string): Boolean; -begin - if FSpringPattern[Length(AArrangement) + 1] in COperationalPatternChars then - begin - AArrangement := AArrangement + COperationalChar; - Result := True; - end - else - Result := False; -end; - -function THotSprings.TryAppendValidationBlock(var AArrangement: string; const ALength: Integer): Boolean; +procedure TBlock.ParseDamages; var i, len: Integer; + damage: TDamage; begin - Result := True; - len := Length(AArrangement); - for i := 1 to ALength do + 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 - if FSpringPattern[len + i] in CDamagedPatternChars then - AArrangement := AArrangement + CDamagedChar + damage.CharsRemaining := 0; + FDamages.Add(damage); + end; +end; + +constructor TBlock.Create(const APattern: string; constref ACombinationsCache: TBlockCombinationsCache); +begin + FPattern := APattern; + FCombinationsCache := ACombinationsCache; + ParseDamages; +end; + +destructor TBlock.Destroy; +begin + FDamages.Free; + inherited Destroy; +end; + +{ TAccumulatedCombinationsMultiIndexStrategy } + +function TAccumulatedCombinationsMultiIndexStrategy.UpdateCombinations(const AValidationResult: TIndexValidationResult; + constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: Integer): TIndexValidationResult; +var + combinations: Int64; +begin + Result := AValidationResult; + if Result = ivrValid then + begin + combinations := CalcCombinations(ACurrentIndexArray, ACurrentIndex); + if combinations = 0 then + Result := ivrSkip + else if ACurrentIndex > 0 then + FAccumulatedCombinations[ACurrentIndex] := combinations * FAccumulatedCombinations[ACurrentIndex - 1] else begin - Result := False; - Break; + SetLength(FAccumulatedCombinations, GetCardinality); + FAccumulatedCombinations[ACurrentIndex] := combinations; end; end; end; +function TAccumulatedCombinationsMultiIndexStrategy.GetCombinations: Int64; +begin + if FAccumulatedCombinations <> nil then + Result := FAccumulatedCombinations[GetCardinality - 1] + else + Result := 0; +end; + +{ TValidationsToBlockAssignments } + +function TValidationsToBlockAssignments.CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: + Integer): Int64; +var + block: TBlock; + start, stop: Integer; +begin + // 'ACurrentIndexArray[i] - 1' denotes the index of the last validation number assigned to 'Block[i]', and the index + // of the first validation number in 'Validation' assigned to 'Block[i + 1]'. If two consecutive values in + // 'ACurrentIndexArray' are the same, then the block in between has no numbers assigned to it. + block := FConditionRecord.Blocks[ACurrentIndex]; + if ACurrentIndex > 0 then + start := ACurrentIndexArray[ACurrentIndex - 1] + else + start := 0; + stop := ACurrentIndexArray[ACurrentIndex] - 1; + if block.Damages.Count > 0 then + Result := FConditionRecord.CalcCombinationsBlock(block, start, stop) + else + Result := FConditionRecord.CalcCombinationsWildcardSequence(Length(block.Pattern), start, stop); +end; + +constructor TValidationsToBlockAssignments.Create(constref AConditionRecord: TConditionRecord); +begin + FConditionRecord := AConditionRecord; +end; + +function TValidationsToBlockAssignments.GetCardinality: Integer; +begin + Result := FConditionRecord.Blocks.Count; +end; + +function TValidationsToBlockAssignments.TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; + const ACurrentIndex: Integer; out AStartIndexValue: Integer): Boolean; +begin + Result := True; + if ACurrentIndex + 1 = GetCardinality then + AStartIndexValue := FConditionRecord.Validation.Count + else if ACurrentIndex > 0 then + AStartIndexValue := Max(ACurrentIndexArray[ACurrentIndex - 1], FConditionRecord.MinIndices[ACurrentIndex]) + else + AStartIndexValue := FConditionRecord.MinIndices[ACurrentIndex]; +end; + +function TValidationsToBlockAssignments.ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; + const ACurrentIndex: Integer): TIndexValidationResult; +var + start: Integer; +begin + if ACurrentIndexArray[ACurrentIndex] > FConditionRecord.Validation.Count then + Result := ivrBacktrack + else begin + if ACurrentIndex > 0 then + start := ACurrentIndexArray[ACurrentIndex - 1] + else + start := 0; + + if FConditionRecord.ValidationLengths[start, ACurrentIndexArray[ACurrentIndex]] + <= Length(FConditionRecord.Blocks[ACurrentIndex].Pattern) then + Result := ivrValid + else + Result := ivrBacktrack; + end; + + Result := UpdateCombinations(Result, ACurrentIndexArray, ACurrentIndex); +end; + +{ TDamageToValidationAssignments } + +function TDamageToValidationAssignments.CalcValidationSpan(constref ACurrentIndexArray: TIndexArray; + const ALastDamageIndex, AValidationNumber: Integer): Integer; +var + spanStart: Integer; +begin + spanStart := ALastDamageIndex; + while (spanStart > 0) and (ACurrentIndexArray[spanStart - 1] = AValidationNumber) do + Dec(spanStart); + Result := FBlock.Damages[ALastDamageIndex].Length; + if spanStart < ALastDamageIndex then + Inc(Result, FBlock.Damages[ALastDamageIndex].Start - FBlock.Damages[spanStart].Start); +end; + +constructor TDamageToValidationAssignments.Create(constref AConditionRecord: TConditionRecord; constref ABlock: TBlock; + const AStartValidationIndex, AStopValidationIndex: Integer); +begin + FConditionRecord := AConditionRecord; + FBlock := ABlock; + FValidationStartIndex := AStartValidationIndex; + FValidationStopIndex := AStopValidationIndex; +end; + +function TDamageToValidationAssignments.GetCardinality: Integer; +begin + Result := FBlock.Damages.Count; +end; + +function TDamageToValidationAssignments.TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; + const ACurrentIndex: Integer; out AStartIndexValue: Integer): Boolean; +begin + Result := True; + if ACurrentIndex > 0 then + AStartIndexValue := ACurrentIndexArray[ACurrentIndex - 1] + else + AStartIndexValue := FValidationStartIndex; +end; + +function TDamageToValidationAssignments.ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; + const ACurrentIndex: Integer): TIndexValidationResult; +var + i, prev: Integer; +begin + i := ACurrentIndexArray[ACurrentIndex]; + prev := ACurrentIndex - 1; + // Checks maximum index value. + if i > FValidationStopIndex then + Result := ivrBacktrack + // Checks if there is enough space after this damage for remaining validation numbers. + else if (i < FValidationStopIndex) + and (FConditionRecord.ValidationLengths[i + 1, FValidationStopIndex + 1] + 1 > FBlock.Damages[ACurrentIndex].CharsRemaining) then + Result := ivrSkip + // Checks if there is enough space before this damage for previous validation numbers. + else if (FValidationStartIndex < i) + and (FConditionRecord.ValidationLengths[FValidationStartIndex, i] + 1 >= FBlock.Damages[ACurrentIndex].Start) then + Result := ivrBacktrack + // Checks if there is enough space between previous and this damage for skipped validation numbers. + else if (ACurrentIndex > 0) + and (ACurrentIndexArray[prev] + 1 < i) + and (FConditionRecord.ValidationLengths[ACurrentIndexArray[prev] + 1, i] + 2 + > FBlock.Damages[ACurrentIndex].Start - FBlock.Damages[prev].Start - FBlock.Damages[prev].Length) then + Result := ivrBacktrack + // Checks if span is small enough to fit within this validation number. + else if FConditionRecord.Validation[i] < CalcValidationSpan(ACurrentIndexArray, ACurrentIndex, i) then + Result := ivrSkip + else + Result := ivrValid; +end; + +{ TValidationPositionOffsets } + +function TValidationPositionOffsets.CalcCombinations(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: + Integer): Int64; +var + space, start, stop: Integer; +begin + stop := FPositionInfos[ACurrentIndex].ValidationIndex - 1; + if ACurrentIndex > 0 then + begin + space := ACurrentIndexArray[ACurrentIndex] - ACurrentIndexArray[ACurrentIndex - 1] + - FConditionRecord.Validation[FPositionInfos[ACurrentIndex - 1].ValidationIndex] - 2; + start := FPositionInfos[ACurrentIndex - 1].ValidationIndex + 1; + Result := FConditionRecord.CalcCombinationsWildcardSequence(space, start, stop); + end + else begin + // Handles first calculated offset. + space := ACurrentIndexArray[0] - 2; + Result := FConditionRecord.CalcCombinationsWildcardSequence(space, FValidationStartIndex, stop); + end; + + if (Result > 0) and (ACurrentIndex + 1 = GetCardinality) then + begin + // Handles last calculated offset. + space := FBlockLength - ACurrentIndexArray[ACurrentIndex] - FConditionRecord.Validation[FPositionInfos.Last.ValidationIndex]; + start := FPositionInfos.Last.ValidationIndex + 1; + Result := Result * FConditionRecord.CalcCombinationsWildcardSequence(space, start, FValidationStopIndex); + end; +end; + +constructor TValidationPositionOffsets.Create(constref AConditionRecord: TConditionRecord; constref APositionInfos: + TValidationPositionInfos; const ABlockLength, AValidationStartIndex, AValidationStopIndex: Integer); +begin + FConditionRecord := AConditionRecord; + FPositionInfos := APositionInfos; + FBlockLength := ABlockLength; + FValidationStartIndex := AValidationStartIndex; + FValidationStopIndex := AValidationStopIndex; + inherited Create; +end; + +function TValidationPositionOffsets.GetCardinality: Integer; +begin + Result := FPositionInfos.Count; +end; + +function TValidationPositionOffsets.TryGetStartIndexValue(constref ACurrentIndexArray: TIndexArray; + const ACurrentIndex: Integer; out AStartIndexValue: Integer): Boolean; +var + info: TValidationPositionInfo; +begin + info := FPositionInfos[ACurrentIndex]; + AStartIndexValue := info.MinStart; + // Adjusts start value to avoid overlap of this validation number with the previous one (the one from previous + // position info). + if ACurrentIndex > 0 then + AStartIndexValue := Max(AStartIndexValue, + ACurrentIndexArray[ACurrentIndex - 1] + FConditionRecord.Validation[FPositionInfos[ACurrentIndex - 1].ValidationIndex] + 1); + Result := True; +end; + +function TValidationPositionOffsets.ValidateIndexValue(constref ACurrentIndexArray: TIndexArray; const ACurrentIndex: + Integer): TIndexValidationResult; +begin + if ACurrentIndexArray[ACurrentIndex] <= FPositionInfos[ACurrentIndex].MaxStart then + Result := ivrValid + else + Result := ivrBacktrack; + + Result := UpdateCombinations(Result, ACurrentIndexArray, ACurrentIndex); +end; + +{ TConditionRecord } + +procedure TConditionRecord.InitValidationLengths; +var + i, j: Integer; +begin + SetLength(FValidationLengths, FValidation.Count + 1, FValidation.Count + 1); + for i := 0 to FValidation.Count do + begin + FValidationLengths[i, i] := 0; + for j := i + 1 to FValidation.Count do + if FValidationLengths[i, j - 1] <> 0 then + FValidationLengths[i, j] := FValidationLengths[i, j - 1] + FValidation[j - 1] + 1 + else + FValidationLengths[i, j] := FValidationLengths[i, j - 1] + FValidation[j - 1] + end; +end; + +procedure TConditionRecord.InitMinIndices; +var + i, j, patternsLength: Integer; +begin + SetLength(FMinIndices, FBlocks.Count - 1); + patternsLength := Length(FBlocks[FBlocks.Count - 1].Pattern); + j := FValidation.Count; + for i := FBlocks.Count - 2 downto 0 do + begin + while (j >= 0) and (FValidationLengths[j, FValidation.Count] <= patternsLength) do + Dec(j); + FMinIndices[i] := j + 1; + patternsLength := patternsLength + 1 + Length(FBlocks[i].Pattern); + end; +end; + +function TConditionRecord.CalcCombinationsBlockSingleValidation(constref ABlock: TBlock; const AIndex: Integer): Int64; +var + len, combinedDamagesLength: Integer; +begin + len := Length(ABlock.Pattern); + if len < FValidation[AIndex] then + Result := 0 + else if ABlock.Damages.Count = 0 then + Result := len - FValidation[AIndex] + 1 + else begin + combinedDamagesLength := ABlock.Damages.Last.Start + ABlock.Damages.Last.Length - ABlock.Damages.First.Start; + if FValidation[AIndex] < combinedDamagesLength then + Result := 0 + else begin + Result := Min(Min(Min( + ABlock.Damages.First.Start, + FValidation[AIndex] - combinedDamagesLength + 1), + len - FValidation[AIndex] + 1), + ABlock.Damages.Last.CharsRemaining + 1); + end; + end; +end; + +function TConditionRecord.CalcCombinationsBlockMultiValidations(constref ABlock: TBlock; constref AIndices: + TIndexArray; const AStartIndex, AStopIndex: Integer): Int64; +var + i, high: Integer; + position: TValidationPositionInfo; + positions: TValidationPositionInfos; + validationPositionOffsets: TValidationPositionOffsets; + offsets: TIndexArray; +begin + positions := TValidationPositionInfos.Create; + high := Length(AIndices) - 1; + // Initializes first info record. + position.ValidationIndex := AIndices[0]; + position.MaxStart := ABlock.Damages[0].Start; + position.MinStart := 1; + for i := 1 to high do + if AIndices[i] <> position.ValidationIndex then + begin + // Finalizes current info record. + position.MaxStart := Min(position.MaxStart, ABlock.Damages[i].Start - 1 - FValidation[position.ValidationIndex]); + position.MinStart := Max(position.MinStart, + ABlock.Damages[i - 1].Start + ABlock.Damages[i - 1].Length - FValidation[position.ValidationIndex]); + positions.Add(position); + // Initializes next info record. + position.ValidationIndex := AIndices[i]; + position.MaxStart := ABlock.Damages[i].Start; + position.MinStart := position.MinStart + FValidationLengths[AIndices[i - 1], AIndices[i]] + 1; + end; + // Finalizes last info record. + position.MaxStart := Min(position.MaxStart, Length(ABlock.Pattern) + 1 - FValidation[position.ValidationIndex]); + position.MinStart := Max(position.MinStart, + ABlock.Damages[high].Start + ABlock.Damages[high].Length - FValidation[position.ValidationIndex]); + positions.Add(position); + + Result := 0; + validationPositionOffsets := TValidationPositionOffsets.Create(Self, positions, Length(ABlock.Pattern), + AStartIndex, AStopIndex); + for offsets in validationPositionOffsets do + Result := Result + validationPositionOffsets.GetCombinations; + validationPositionOffsets.Free; + + positions.Free; +end; + +function TConditionRecord.CalcValidationsId(const AStartIndex, AStopIndex: Integer): Int64; +var + i: Integer; +begin + // Requires 'FValidations[i] < 32' for each 'i' and 'AStopIndex - AStartIndex < 12'. + Result := FValidation[AStartIndex]; + for i := AStartIndex + 1 to AStopIndex do + Result := (Result shl 5) or FValidation[i]; +end; + +constructor TConditionRecord.Create(constref ACombinationsCache: TCombinationsCache); +begin + FBlocks := TBlocks.Create; + FValidation := TIntegerList.Create; + FCombinationsCache := ACombinationsCache; +end; + +destructor TConditionRecord.Destroy; +begin + FBlocks.Free; + FValidation.Free; + inherited Destroy; +end; + +procedure TConditionRecord.AddBlocks(const APattern: string); +var + split: TStringArray; + part: string; + blockCache: TBlockCombinationsCache; +begin + split := APattern.Split([COperationalChar]); + for part in split do + if Length(part) > 0 then + begin + if not FCombinationsCache.TryGetValue(part, blockCache) then + begin + blockCache := TBlockCombinationsCache.Create; + FCombinationsCache.Add(part, blockCache); + end; + FBlocks.Add(TBlock.Create(part, blockCache)); + end; +end; + +function TConditionRecord.GenerateBlockAssignments: Int64; +var + validationsToBlockAssignments: TValidationsToBlockAssignments; + indices: TIndexArray; +begin + InitValidationLengths; + InitMinIndices; + + Result := 0; + validationsToBlockAssignments := TValidationsToBlockAssignments.Create(Self); + for indices in validationsToBlockAssignments do + Result := Result + validationsToBlockAssignments.GetCombinations; + validationsToBlockAssignments.Free; +end; + +function TConditionRecord.CalcCombinationsBlock(constref ABlock: TBlock; const AStartIndex, AStopIndex: Integer): Int64; +var + validationsId: Int64; + indices: TIndexArray; + damageToValidationAssignments: TDamageToValidationAssignments; +begin + // No validation number assigned to this block. + if AStartIndex > AStopIndex then + begin + if ABlock.Damages.Count = 0 then + Result := 1 + else + Result := 0; + end + // One validation number assigned to this block. + else if AStartIndex = AStopIndex then + Result := CalcCombinationsBlockSingleValidation(ABlock, AStartIndex) + // Multiple validation numbers assigned to this block. Checks cache first. + else begin + validationsId := CalcValidationsId(AStartIndex, AStopIndex); + if not ABlock.CombinationsCache.TryGetValue(validationsId, Result) then + begin + Result := 0; + + // Assigns validation numbers to specific damages. + damageToValidationAssignments := TDamageToValidationAssignments.Create(Self, ABlock, AStartIndex, AStopIndex); + for indices in damageToValidationAssignments do + Result := Result + CalcCombinationsBlockMultiValidations(ABlock, indices, AStartIndex, AStopIndex); + damageToValidationAssignments.Free; + + ABlock.CombinationsCache.Add(validationsId, Result); + end; + end; +end; + +function TConditionRecord.CalcCombinationsWildcardSequence(const ASequenceLength, AStartIndex, AStopIndex: Integer): + Int64; +var + count, freedoms: Integer; +begin + if AStartIndex < AStopIndex + 1 then + begin + count := AStopIndex + 1 - AStartIndex; + freedoms := ASequenceLength - FValidationLengths[AStartIndex, AStopIndex + 1]; + if freedoms >= 0 then + Result := BinomialCoefficients.Get(count + freedoms, freedoms) + else + Result := 0; + end + else + Result := 1; +end; + +{ THotSprings } + constructor THotSprings.Create; begin - FValidation := specialize TList.Create; + FCombinationsCache := TCombinationsCache.Create([doOwnsValues]); end; destructor THotSprings.Destroy; begin - FValidation.Free; + FCombinationsCache.Free; inherited Destroy; end; procedure THotSprings.ProcessDataLine(const ALine: string); var - split: TStringArray; - i, val, maxFreeOperationalCount: Integer; + conditionRecord1, conditionRecord2: TConditionRecord; + mainSplit, split: TStringArray; + part, unfolded: string; + i: Integer; begin - FValidation.Clear; - split := ALine.Split([' ', ',']); - FSpringPattern := split[0]; + conditionRecord1 := TConditionRecord.Create(FCombinationsCache); + conditionRecord2 := TConditionRecord.Create(FCombinationsCache); - maxFreeOperationalCount := Length(FSpringPattern) - Length(split) + 2; - for i := 1 to Length(split) - 1 do - begin - val := StrToInt(split[i]); - FValidation.Add(val); - Dec(maxFreeOperationalCount, val); - end; + mainSplit := ALine.Split([' ']); - ExtendArrangement('', maxFreeOperationalCount, 0); + // Adds blocks for part 1. + conditionRecord1.AddBlocks(mainSplit[0]); + + // Adds blocks for part 2. + unfolded := mainSplit[0]; + for i := 2 to CPart2Repetition do + unfolded := unfolded + CWildcardChar + mainSplit[0]; + conditionRecord2.AddBlocks(unfolded); + + // Adds validation numbers. + split := mainSplit[1].Split([',']); + for part in split do + conditionRecord1.Validation.Add(StrToInt(part)); + for i := 1 to CPart2Repetition do + conditionRecord2.Validation.AddRange(conditionRecord1.Validation); + + FPart1 := FPart1 + conditionRecord1.GenerateBlockAssignments; + FPart2 := FPart2 + conditionRecord2.GenerateBlockAssignments; + + conditionRecord1.Free; + conditionRecord2.Free; end; procedure THotSprings.Finish; diff --git a/tests/AdventOfCodeFPCUnit.lpi b/tests/AdventOfCodeFPCUnit.lpi index 0d0c019..7ee1927 100644 --- a/tests/AdventOfCodeFPCUnit.lpi +++ b/tests/AdventOfCodeFPCUnit.lpi @@ -152,6 +152,10 @@ + + + + diff --git a/tests/AdventOfCodeFPCUnit.lpr b/tests/AdventOfCodeFPCUnit.lpr index 2c2a60e..4b270c1 100644 --- a/tests/AdventOfCodeFPCUnit.lpr +++ b/tests/AdventOfCodeFPCUnit.lpr @@ -10,7 +10,7 @@ uses UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases, UPulsePropagationTestCases, UStepCounterTestCases, USandSlabsTestCases, ULongWalkTestCases, UNeverTellMeTheOddsTestCases, USnowverloadTestCases, UBigIntTestCases, UPolynomialTestCases, - UPolynomialRootsTestCases; + UPolynomialRootsTestCases, UBinomialCoefficientsTestCases; {$R *.res} diff --git a/tests/UBinomialCoefficientsTestCases.pas b/tests/UBinomialCoefficientsTestCases.pas new file mode 100644 index 0000000..4de87b4 --- /dev/null +++ b/tests/UBinomialCoefficientsTestCases.pas @@ -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 . +} + +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. + diff --git a/tests/UHotSpringsTestCases.pas b/tests/UHotSpringsTestCases.pas index 03f9a92..cac94b0 100644 --- a/tests/UHotSpringsTestCases.pas +++ b/tests/UHotSpringsTestCases.pas @@ -33,6 +33,7 @@ type function CreateSolver: ISolver; override; published procedure TestPart1; + procedure TestPart2; end; { THotSpringsTestCase } @@ -40,14 +41,20 @@ type THotSpringsTestCase = class(TSolverTestCase) protected function CreateSolver: ISolver; override; - procedure TestSingleLine(const ALine: string; const AValue: Integer); + procedure TestSingleLine(const ALine: string); published - procedure TestExampleLine1; - procedure TestExampleLine2; - procedure TestExampleLine3; - procedure TestExampleLine4; - procedure TestExampleLine5; - procedure TestExampleLine6; + procedure TestExampleLine1Part1; + procedure TestExampleLine2Part1; + procedure TestExampleLine3Part1; + procedure TestExampleLine4Part1; + procedure TestExampleLine5Part1; + procedure TestExampleLine6Part1; + procedure TestExampleLine1Part2; + procedure TestExampleLine2Part2; + procedure TestExampleLine3Part2; + procedure TestExampleLine4Part2; + procedure TestExampleLine5Part2; + procedure TestExampleLine6Part2; end; implementation @@ -64,6 +71,11 @@ begin AssertEquals(21, FSolver.GetResultPart1); end; +procedure THotSpringsExampleTestCase.TestPart2; +begin + AssertEquals(525152, FSolver.GetResultPart2); +end; + { THotSpringsTestCase } function THotSpringsTestCase.CreateSolver: ISolver; @@ -71,42 +83,83 @@ begin Result := THotSprings.Create; end; -procedure THotSpringsTestCase.TestSingleLine(const ALine: string; const AValue: Integer); +procedure THotSpringsTestCase.TestSingleLine(const ALine: string); begin FSolver.Init; FSolver.ProcessDataLine(ALine); FSolver.Finish; - AssertEquals(AValue, FSolver.GetResultPart1); end; -procedure THotSpringsTestCase.TestExampleLine1; +procedure THotSpringsTestCase.TestExampleLine1Part1; begin - TestSingleLine('???.### 1,1,3', 1); + TestSingleLine('???.### 1,1,3'); + AssertEquals(1, FSolver.GetResultPart1); end; -procedure THotSpringsTestCase.TestExampleLine2; +procedure THotSpringsTestCase.TestExampleLine2Part1; begin - TestSingleLine('.??..??...?##. 1,1,3', 4); + TestSingleLine('.??..??...?##. 1,1,3'); + AssertEquals(4, FSolver.GetResultPart1); end; -procedure THotSpringsTestCase.TestExampleLine3; +procedure THotSpringsTestCase.TestExampleLine3Part1; begin - TestSingleLine('?#?#?#?#?#?#?#? 1,3,1,6', 1); + TestSingleLine('?#?#?#?#?#?#?#? 1,3,1,6'); + AssertEquals(1, FSolver.GetResultPart1); end; -procedure THotSpringsTestCase.TestExampleLine4; +procedure THotSpringsTestCase.TestExampleLine4Part1; begin - TestSingleLine('????.#...#... 4,1,1', 1); + TestSingleLine('????.#...#... 4,1,1'); + AssertEquals(1, FSolver.GetResultPart1); end; -procedure THotSpringsTestCase.TestExampleLine5; +procedure THotSpringsTestCase.TestExampleLine5Part1; begin - TestSingleLine('????.######..#####. 1,6,5', 4); + TestSingleLine('????.######..#####. 1,6,5'); + AssertEquals(4, FSolver.GetResultPart1); end; -procedure THotSpringsTestCase.TestExampleLine6; +procedure THotSpringsTestCase.TestExampleLine6Part1; 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; initialization