Merge branch 'day24-analytical'
This commit is contained in:
		
						commit
						859a5db921
					
				| @ -137,6 +137,18 @@ | ||||
|         <Filename Value="solvers\UNeverTellMeTheOdds.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|       <Unit> | ||||
|         <Filename Value="UBigInt.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|       <Unit> | ||||
|         <Filename Value="UPolynomial.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|       <Unit> | ||||
|         <Filename Value="UPolynomialRoots.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|     </Units> | ||||
|   </ProjectOptions> | ||||
|   <CompilerOptions> | ||||
|  | ||||
							
								
								
									
										763
									
								
								UBigInt.pas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										763
									
								
								UBigInt.pas
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,763 @@ | ||||
| { | ||||
|   Solutions to the Advent Of Code. | ||||
|   Copyright (C) 2022-2024  Stefan Müller | ||||
| 
 | ||||
|   This program is free software: you can redistribute it and/or modify it under | ||||
|   the terms of the GNU General Public License as published by the Free Software | ||||
|   Foundation, either version 3 of the License, or (at your option) any later | ||||
|   version. | ||||
| 
 | ||||
|   This program is distributed in the hope that it will be useful, but WITHOUT | ||||
|   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||
|   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. | ||||
| 
 | ||||
|   You should have received a copy of the GNU General Public License along with | ||||
|   this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| } | ||||
| 
 | ||||
| unit UBigInt; | ||||
| 
 | ||||
| {$mode ObjFPC}{$H+} | ||||
| 
 | ||||
| interface | ||||
| 
 | ||||
| uses | ||||
|   Classes, SysUtils, Math; | ||||
| 
 | ||||
| type | ||||
|   TDigits = array of Cardinal; | ||||
| 
 | ||||
|   { TBigInt } | ||||
| 
 | ||||
|   // This is an abbreviated reimplementation in Freepascal of a C# class created in 2022. | ||||
|   TBigInt = object | ||||
|   private | ||||
|     FDigits: TDigits; | ||||
|     FIsNegative: Boolean; | ||||
| 
 | ||||
|     function GetSign: Integer; | ||||
| 
 | ||||
|     // Copies consecutive digits from this BigInt to create a new one. The result will be positive. Leading zeros are | ||||
|     // removed from the result, but AIndex + ACount must not exceed the number of digits of this BigInt. | ||||
|     // AIndex is the first (least significant) digit to be taken. The digit with this index will become the 0th digit of | ||||
|     // the new BigInt. | ||||
|     // ACount is the number of consecutive digits to be taken, and the number of digits of the result. | ||||
|     function GetSegment(const AIndex, ACount: Integer): TBigInt; | ||||
| 
 | ||||
|     // Compares the absolute value of this TBigInt object to the absolute value of another one. Returns -1 if this | ||||
|     // object is less than AOther, 1 if this object is greater than AOther, and 0 if they are equal. | ||||
|     function CompareToAbsoluteValues(constref AOther: TBigInt): Integer; | ||||
| 
 | ||||
|     class function GetZero: TBigInt; static; | ||||
|     class function GetOne: TBigInt; static; | ||||
| 
 | ||||
|     // Adds A and B, ignoring their signs and using ReturnNegative instead. The result is | ||||
|     //   Sign * (Abs(A) + Abs(B)), | ||||
|     // where Sign is 1 for ReturnNegative = False and -1 otherwise. | ||||
|     class function AddAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; static; | ||||
| 
 | ||||
|     // Subtracts B from A, ignoring their signs. However, the result might be negative, and the sign can be reversed by | ||||
|     // setting ReturnNegative to True. The result is | ||||
|     //   Sign * (Abs(A) - Abs(B)), | ||||
|     // where Sign is 1 for ReturnNegative = False and -1 otherwise. | ||||
|     class function SubtractAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; static; | ||||
| 
 | ||||
|     // Multiplies A and B, ignoring their signs and using ReturnNegative instead. This multiplication uses a recursive | ||||
|     // implementation of the Karatsuba algorithm. See | ||||
|     // https://www.geeksforgeeks.org/karatsuba-algorithm-for-fast-multiplication-using-divide-and-conquer-algorithm/ | ||||
|     // The result is | ||||
|     //   Sign * (Abs(a) * Abs(b)) | ||||
|     // where Sign is 1 for ReturnNegative = False and -1 otherwise. | ||||
|     class function MultiplyAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; static; | ||||
| 
 | ||||
|     class function FromHexOrBinString(const AValue: string; const AFromBase: Integer): TBigInt; static; | ||||
|     class function ConvertDigitBlock(const AValue: string; var AStartIndex: Integer; const ACharBlockSize, AFromBase: | ||||
|       Integer): Cardinal; | ||||
|   public | ||||
|     property IsNegative: Boolean read FIsNegative; | ||||
|     property Sign: Integer read GetSign; | ||||
|     class property Zero: TBigInt read GetZero; | ||||
|     class property One: TBigInt read GetOne; | ||||
| 
 | ||||
|     // Returns the index of the most significant bit, i.e. returns integer k, where 2^k is the largest power of 2 that | ||||
|     // is less than or equal to the absolute value of the number itself. Returns -1 if the given number is 0. | ||||
|     function GetMostSignificantBitIndex: Int64; | ||||
|     function CompareTo(constref AOther: TBigInt): Integer; | ||||
|     function TryToInt64(out AOutput: Int64): Boolean; | ||||
|     // TODO: ToString is currently for debug output only. | ||||
|     function ToString: string; | ||||
|     class function FromInt64(const AValue: Int64): TBigInt; static; | ||||
|     class function FromHexadecimalString(const AValue: string): TBigInt; static; | ||||
|     class function FromBinaryString(const AValue: string): TBigInt; static; | ||||
|   end; | ||||
| 
 | ||||
|   TBigIntArray = array of TBigInt; | ||||
| 
 | ||||
|   { Operators } | ||||
| 
 | ||||
|   operator := (const A: Int64): TBigInt; | ||||
|   operator - (const A: TBigInt): TBigInt; | ||||
|   operator + (const A, B: TBigInt): TBigInt; | ||||
|   operator - (const A, B: TBigInt): TBigInt; | ||||
|   operator * (const A, B: TBigInt): TBigInt; | ||||
|   operator shl (const A: TBigInt; const B: Integer): TBigInt; | ||||
|   operator shr (const A: TBigInt; const B: Integer): TBigInt; | ||||
|   operator = (const A, B: TBigInt): Boolean; | ||||
|   operator <> (const A, B: TBigInt): Boolean; | ||||
|   operator < (const A, B: TBigInt): Boolean; | ||||
|   operator <= (const A, B: TBigInt): Boolean; | ||||
|   operator > (const A, B: TBigInt): Boolean; | ||||
|   operator >= (const A, B: TBigInt): Boolean; | ||||
| 
 | ||||
| implementation | ||||
| 
 | ||||
| const | ||||
|   CBase = Cardinal.MaxValue + 1; | ||||
|   CMaxDigit = Cardinal.MaxValue; | ||||
|   CDigitSize = SizeOf(Cardinal); | ||||
|   CBitsPerDigit = CDigitSize * 8; | ||||
|   CHalfBits = CBitsPerDigit >> 1; | ||||
|   CHalfDigitMax = (1 << CHalfBits) - 1; | ||||
| 
 | ||||
|   CZero: TBigInt = (FDigits: (0); FIsNegative: False); | ||||
|   COne: TBigInt = (FDigits: (1); FIsNegative: False); | ||||
| 
 | ||||
| { TBigInt } | ||||
| 
 | ||||
| function TBigInt.GetSign: Integer; | ||||
| begin | ||||
|   if FIsNegative then | ||||
|     Result := -1 | ||||
|   else if (Length(FDigits) > 1) or (FDigits[0] <> 0) then | ||||
|     Result := 1 | ||||
|   else | ||||
|     Result := 0; | ||||
| end; | ||||
| 
 | ||||
| function TBigInt.GetSegment(const AIndex, ACount: Integer): TBigInt; | ||||
| var | ||||
|   trimmedCount: Integer; | ||||
| begin | ||||
|   trimmedCount := ACount; | ||||
|   while (trimmedCount > 1) and (FDigits[AIndex + trimmedCount - 1] = 0) do | ||||
|     Dec(trimmedCount); | ||||
|   Result.FDigits := Copy(FDigits, AIndex, trimmedCount); | ||||
|   Result.FIsNegative := False; | ||||
| end; | ||||
| 
 | ||||
| function TBigInt.CompareToAbsoluteValues(constref AOther: TBigInt): Integer; | ||||
| var | ||||
|   i: Integer; | ||||
| begin | ||||
|   Result := Length(FDigits) - Length(AOther.FDigits); | ||||
|   if Result = 0 then | ||||
|   begin | ||||
|     for i := High(FDigits) downto 0 do | ||||
|       if FDigits[i] < AOther.FDigits[i] then | ||||
|       begin | ||||
|        Result := -1; | ||||
|        Break; | ||||
|       end | ||||
|       else if FDigits[i] > AOther.FDigits[i] then | ||||
|       begin | ||||
|        Result := 1; | ||||
|        Break; | ||||
|       end; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.GetZero: TBigInt; | ||||
| begin | ||||
|   Result := CZero; | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.GetOne: TBigInt; | ||||
| begin | ||||
|   Result := COne; | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.AddAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; | ||||
| var | ||||
|   i, j, lenA, lenB, len, shorter: Integer; | ||||
|   carry: Cardinal; | ||||
| begin | ||||
|   lenA := Length(AA.FDigits); | ||||
|   lenB := Length(AB.FDigits); | ||||
| 
 | ||||
|   // Initializes the digits array of the result, with a simple test to try to predict a carry-over into a new digit. The | ||||
|   // result could still carry into new digit depending on lower digits (carry over multiple digits), which would be | ||||
|   // handled at the end. | ||||
|   if lenA = lenB then | ||||
|     if CMaxDigit - AA.FDigits[lenA - 1] < AB.FDigits[lenB - 1] then | ||||
|       // Result will carry into new digit. | ||||
|       SetLength(Result.FDigits, lenA + 1) | ||||
|     else | ||||
|       SetLength(Result.FDigits, lenA) | ||||
|   else | ||||
|     SetLength(Result.FDigits, Max(lenA, lenB)); | ||||
|   len := Length(Result.FDigits); | ||||
| 
 | ||||
|   // Calculates the new digits from less to more significant until the end of the shorter operand is reached. | ||||
|   shorter := Min(Length(AA.FDigits), Length(AB.FDigits)); | ||||
|   i := 0; | ||||
|   carry := 0; | ||||
|   while i < shorter do | ||||
|   begin | ||||
|     if (AB.FDigits[i] = CMaxDigit) and (carry > 0) then | ||||
|     begin | ||||
|       Result.FDigits[i] := AA.FDigits[i]; | ||||
|       carry := 1; | ||||
|     end | ||||
|     else | ||||
|       if CMaxDigit - AA.FDigits[i] < AB.FDigits[i] + carry then | ||||
|       begin | ||||
|         Result.FDigits[i] := AB.FDigits[i] + carry - 1 - (CMaxDigit - AA.FDigits[i]); | ||||
|         carry := 1; | ||||
|       end | ||||
|       else begin | ||||
|         Result.FDigits[i] := AA.FDigits[i] + AB.FDigits[i] + carry; | ||||
|         carry := 0; | ||||
|       end; | ||||
|     Inc(i); | ||||
|   end; | ||||
| 
 | ||||
|   // Copies the missing unchanged digits from the longer operand to the result, if any, before applying remaining | ||||
|   // carry-overs. This avoids additional tests for finding the shorter digit array. | ||||
|   if (i < lenA) or (i < lenB) then | ||||
|     if lenA >= lenB then | ||||
|       for j := i to len - 1 do | ||||
|         Result.FDigits[j] := AA.FDigits[j] | ||||
|     else | ||||
|       for j := i to len - 1 do | ||||
|         Result.FDigits[j] := AB.FDigits[j]; | ||||
| 
 | ||||
|   // Applies the remaining carry-overs until the end of the prepared result digit array. | ||||
|   while (carry > 0) and (i < len) do | ||||
|   begin | ||||
|     if Result.FDigits[i] = CMaxDigit then | ||||
|       Result.FDigits[i] := 0 | ||||
|     else begin | ||||
|       Inc(Result.FDigits[i]); | ||||
|       carry := 0; | ||||
|     end; | ||||
|     Inc(i); | ||||
|   end; | ||||
| 
 | ||||
|   // Applies the carry-over into a new digit that was not anticipated in the initialization at the top (carry over | ||||
|   // multiple digits). | ||||
|   if carry > 0 then | ||||
|     Insert(1, Result.FDigits, len); | ||||
|   Result.FIsNegative := AReturnNegative; | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.SubtractAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; | ||||
| var | ||||
|   a, b: TBigInt; | ||||
|   carry: Cardinal; | ||||
|   compare, i, j, lastNonZeroDigitIndex, len: Integer; | ||||
| begin | ||||
|   // Establishes the operand order, such that Abs(a) is not less than Abs(b). | ||||
|   compare := AA.CompareToAbsoluteValues(AB); | ||||
|   if compare = 0 then | ||||
|   begin | ||||
|     Result := Zero; | ||||
|     Exit; | ||||
|   end; | ||||
| 
 | ||||
|   if compare > 0 then | ||||
|   begin | ||||
|     a := AA; | ||||
|     b := AB; | ||||
|     Result.FIsNegative := AReturnNegative; | ||||
|   end | ||||
|   else begin | ||||
|     a := AB; | ||||
|     b := AA; | ||||
|     Result.FIsNegative := not AReturnNegative; | ||||
|   end; | ||||
| 
 | ||||
|   // Calculates the new digits from less to more significant until the end of the shorter operand is reached and all | ||||
|   // carry-overs have been applied. | ||||
|   len := Length(a.FDigits); | ||||
|   SetLength(Result.FDigits, len); | ||||
|   carry := 0; | ||||
|   // Tracks leading zeros for the trim below. | ||||
|   lastNonZeroDigitIndex := 0; | ||||
|   i := 0; | ||||
|   while i < Length(b.FDigits) do | ||||
|   begin | ||||
|     if (a.FDigits[i] = b.FDigits[i]) and (carry > 0) then | ||||
|     begin | ||||
|       Result.FDigits[i] := CMaxDigit; | ||||
|       carry := 1; | ||||
|       lastNonZeroDigitIndex := i; | ||||
|     end | ||||
|     else begin | ||||
|       if a.FDigits[i] < b.FDigits[i] then | ||||
|       begin | ||||
|         Result.FDigits[i] := CMaxDigit - (b.FDigits[i] - a.FDigits[i]) + 1 - carry; | ||||
|         carry := 1; | ||||
|       end | ||||
|       else begin | ||||
|         Result.FDigits[i] := a.FDigits[i] - b.FDigits[i] - carry; | ||||
|         carry := 0; | ||||
|       end; | ||||
|       if Result.FDigits[i] > 0 then | ||||
|         lastNonZeroDigitIndex := i; | ||||
|     end; | ||||
|     Inc(i); | ||||
|   end; | ||||
|   while carry > 0 do | ||||
|   begin | ||||
|     if a.FDigits[i] = 0 then | ||||
|     begin | ||||
|       Result.FDigits[i] := CMaxDigit; | ||||
|       lastNonZeroDigitIndex := i; | ||||
|     end | ||||
|     else begin | ||||
|       Result.FDigits[i] := a.FDigits[i] - carry; | ||||
|       carry := 0; | ||||
|       if Result.FDigits[i] > 0 then | ||||
|         lastNonZeroDigitIndex := i; | ||||
|     end; | ||||
|     Inc(i); | ||||
|   end; | ||||
| 
 | ||||
|   // Copies the missing unchanged digits from the longer operand to the result, if any. If there are none, then no trim | ||||
|   // needs to occur because the most significant digit is not zero. | ||||
|   if i < len then | ||||
|     for j := i to len - 1 do | ||||
|       Result.FDigits[j] := a.FDigits[j] | ||||
|   else if (lastNonZeroDigitIndex + 1 < len) then | ||||
|     // Trims leading zeros from the digits array. | ||||
|     Delete(Result.FDigits, lastNonZeroDigitIndex + 1, len - lastNonZeroDigitIndex - 1); | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.MultiplyAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; | ||||
| var | ||||
|   lenA, lenB, lenMax, floorHalfLength, ceilHalfLength: Integer; | ||||
|   a1, a0, b1, b0, a1b1, a0b0: Cardinal; | ||||
|   am, bm, middle, biga1, biga0, bigb1, bigb0, biga1b1, biga0b0: TBigInt; | ||||
| begin | ||||
|   lenA := Length(AA.FDigits); | ||||
|   lenB := Length(AB.FDigits); | ||||
|   if (lenA <= 1) and (lenB <= 1) then | ||||
|   begin | ||||
|     if (AA.FDigits[0] <= CHalfDigitMax) and (AB.FDigits[0] <= CHalfDigitMax) then | ||||
|       if (AA.FDigits[0] = 0) or (AB.FDigits[0] = 0) then | ||||
|         Result := Zero | ||||
|       else begin | ||||
|         Result.FDigits := TDigits.Create(AA.FDigits[0] * AB.FDigits[0]); | ||||
|         Result.FIsNegative := AReturnNegative; | ||||
|       end | ||||
|     else begin | ||||
|       // a1, a0, b1, b0 use only the lower (less significant) half of the bits of a digit, so the product of any two of | ||||
|       // these fits in one digit. | ||||
|       a1 := AA.FDigits[0] >> CHalfBits; | ||||
|       a0 := AA.FDigits[0] and CHalfDigitMax; | ||||
|       b1 := AB.FDigits[0] >> CHalfBits; | ||||
|       b0 := AB.FDigits[0] and CHalfDigitMax; | ||||
|       a1b1 := a1 * b1; | ||||
|       a0b0 := a0 * b0; | ||||
| 
 | ||||
|       if a1b1 > 0 then | ||||
|         Result.FDigits := TDigits.Create(a0b0, a1b1) | ||||
|       else | ||||
|         Result.FDigits := TDigits.Create(a0b0); | ||||
|       Result.FIsNegative := AReturnNegative; | ||||
| 
 | ||||
|       // The result of (a1 + a0) * (b1 + b0) might not fit in one digit, so one last recursion step is necessary. | ||||
|       am := FromInt64(a1 + a0); | ||||
|       bm := FromInt64(b1 + b0); | ||||
|       middle := (MultiplyAbsoluteValues(am, bm, False) - a1b1 - a0b0) << CHalfBits; | ||||
|       if AReturnNegative then | ||||
|         Result := Result - middle | ||||
|       else | ||||
|         Result := Result + middle; | ||||
|     end; | ||||
|   end | ||||
|   else begin | ||||
|     // Calculates where to split the two numbers. | ||||
|     lenMax := Max(lenA, lenB); | ||||
|     floorHalfLength := lenMax >> 1; | ||||
|     ceilHalfLength := lenMax - floorHalfLength; | ||||
| 
 | ||||
|     // Performs one recursion step. | ||||
|     if ceilHalfLength < lenA then | ||||
|     begin | ||||
|       biga1 := AA.GetSegment(ceilHalfLength, lenA - ceilHalfLength); | ||||
|       biga0 := AA.GetSegment(0, ceilHalfLength); | ||||
| 
 | ||||
|       if ceilHalfLength < lenB then | ||||
|       begin | ||||
|         bigb1 := AB.GetSegment(ceilHalfLength, lenB - ceilHalfLength); | ||||
|         bigb0 := AB.GetSegment(0, ceilHalfLength); | ||||
|         biga1b1 := MultiplyAbsoluteValues(biga1, bigb1, AReturnNegative); | ||||
|         biga0b0 := MultiplyAbsoluteValues(biga0, bigb0, AReturnNegative); | ||||
|         Result := (biga1b1 << (2 * ceilHalfLength * CBitsPerDigit)) | ||||
|           + ((MultiplyAbsoluteValues(biga1 + biga0, bigb1 + bigb0, AReturnNegative) - biga1b1 - biga0b0) << (ceilHalfLength * CBitsPerDigit)) | ||||
|           + biga0b0; | ||||
|       end | ||||
|       else begin | ||||
|         biga0b0 := MultiplyAbsoluteValues(biga0, AB, AReturnNegative); | ||||
|         Result := ((MultiplyAbsoluteValues(biga1 + biga0, AB, AReturnNegative) - biga0b0) << (ceilHalfLength * CBitsPerDigit)) + biga0b0; | ||||
|       end; | ||||
|     end | ||||
|     else begin | ||||
|       bigb1 := AB.GetSegment(ceilHalfLength, lenB - ceilHalfLength); | ||||
|       bigb0 := AB.GetSegment(0, ceilHalfLength); | ||||
|       biga0b0 := MultiplyAbsoluteValues(AA, bigb0, AReturnNegative); | ||||
|       Result := ((MultiplyAbsoluteValues(AA, bigb1 + bigb0, AReturnNegative) - biga0b0) << (ceilHalfLength * CBitsPerDigit)) + biga0b0; | ||||
|     end; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.FromHexOrBinString(const AValue: string; const AFromBase: Integer): TBigInt; | ||||
| var | ||||
|   charBlockSize, offset, i, j, k, remainder: Integer; | ||||
|   d: Cardinal; | ||||
| begin | ||||
|   // 2 ^ (32 / charBlockSize) = AFromBase | ||||
|   case AFromBase of | ||||
|     2: charBlockSize := 32; | ||||
|     16: charBlockSize := 8; | ||||
|   end; | ||||
| 
 | ||||
|   if AValue[1] = '-' then | ||||
|   begin | ||||
|     offset := 2; | ||||
|     Result.FIsNegative := True; | ||||
|   end | ||||
|   else begin | ||||
|     offset := 1; | ||||
|     Result.FIsNegative := False; | ||||
|   end; | ||||
| 
 | ||||
|   // Calculates the first (most significant) digit d of the result. | ||||
|   DivMod(AValue.Length - offset + 1, charBlockSize, i, remainder); | ||||
|   k := offset; | ||||
|   d := 0; | ||||
|   // Checks the first block of chars that is not a full block. | ||||
|   if remainder > 0 then | ||||
|     d := ConvertDigitBlock(AValue, k, remainder, AFromBase); | ||||
|   // Checks full blocks of chars for first digit. | ||||
|   while (d = 0) and (i > 0) do | ||||
|   begin | ||||
|     Dec(i); | ||||
|     d := ConvertDigitBlock(AValue, k, charBlockSize, AFromBase); | ||||
|   end; | ||||
| 
 | ||||
|   // Checks for zero. | ||||
|   if (d = 0) and (i = 0) then | ||||
|     Result := Zero | ||||
|   else begin | ||||
|     // Initializes the array of digits. | ||||
|     SetLength(Result.FDigits, i + 1); | ||||
|     Result.FDigits[i] := d; | ||||
| 
 | ||||
|     // Calculates the other digits. | ||||
|     for j := i - 1 downto 0 do | ||||
|       Result.FDigits[j] := ConvertDigitBlock(AValue, k, charBlockSize, AFromBase); | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.ConvertDigitBlock(const AValue: string; var AStartIndex: Integer; const ACharBlockSize, | ||||
|   AFromBase: Integer): Cardinal; | ||||
| var | ||||
|   part: string; | ||||
| begin | ||||
|   part := Copy(AValue, AStartIndex, ACharBlockSize); | ||||
|   Inc(AStartIndex, ACharBlockSize); | ||||
|   case AFromBase of | ||||
|     2: part := '%' + part; | ||||
|     8: part := '&' + part; | ||||
|     16: part := '$' + part; | ||||
|   end; | ||||
|   Result := StrToDWord(part); | ||||
| end; | ||||
| 
 | ||||
| function TBigInt.GetMostSignificantBitIndex: Int64; | ||||
| var | ||||
|   high, i: Integer; | ||||
| begin | ||||
|   high := Length(FDigits) - 1; | ||||
|   if (high = 0) and (FDigits[0] = 0) then | ||||
|     Result := -1 | ||||
|   else begin | ||||
|     i := CBitsPerDigit - 1; | ||||
|     while ((1 << i) and FDigits[high]) = 0 do | ||||
|       Dec(i); | ||||
|     Result := high * CBitsPerDigit + i; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| function TBigInt.CompareTo(constref AOther: TBigInt): Integer; | ||||
| begin | ||||
|   if FIsNegative = AOther.FIsNegative then | ||||
|     Result := CompareToAbsoluteValues(AOther) | ||||
|   else | ||||
|     Result := 1; | ||||
|   if FIsNegative then | ||||
|     Result := -Result; | ||||
| end; | ||||
| 
 | ||||
| function TBigInt.TryToInt64(out AOutput: Int64): Boolean; | ||||
| begin | ||||
|   AOutput := 0; | ||||
|   Result := False; | ||||
|   case Length(FDigits) of | ||||
|     0: Result := True; | ||||
|     1: begin | ||||
|       AOutput := FDigits[0]; | ||||
|       if FIsNegative then | ||||
|         AOutput := -AOutput; | ||||
|       Result := True; | ||||
|     end; | ||||
|     2: begin | ||||
|       if FDigits[1] <= Integer.MaxValue then | ||||
|       begin | ||||
|         AOutput := FDigits[1] * CBase + FDigits[0]; | ||||
|         if FIsNegative then | ||||
|           AOutput := -AOutput; | ||||
|         Result := True; | ||||
|       end | ||||
|       else if (FDigits[1] = Integer.MaxValue + 1) and (FDigits[0] = 0) and FIsNegative then | ||||
|       begin | ||||
|         AOutput := Int64.MinValue; | ||||
|         Result := True; | ||||
|       end; | ||||
|     end; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| function TBigInt.ToString: string; | ||||
| var | ||||
|   i: Integer; | ||||
| begin | ||||
|   if FIsNegative then | ||||
|     Result := '(-' | ||||
|   else | ||||
|     Result := ''; | ||||
|   for i := 0 to Length(FDigits) - 2 do | ||||
|     Result := Result + '(' + IntToStr(FDigits[i]) + ' + 2^32 * '; | ||||
|   Result := Result + IntToStr(FDigits[Length(FDigits) - 1]); | ||||
|   for i := 0 to Length(FDigits) - 2 do | ||||
|     Result := Result + ')'; | ||||
|   if FIsNegative then | ||||
|     Result := Result + ')' | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.FromInt64(const AValue: Int64): TBigInt; | ||||
| var | ||||
|   absVal: Int64; | ||||
| begin | ||||
|   if AValue <> Int64.MinValue then | ||||
|   begin | ||||
|     absVal := Abs(AValue); | ||||
|     if absVal >= CBase then | ||||
|       Result.FDigits := TDigits.Create(absVal mod CBase, absVal div CBase) | ||||
|     else | ||||
|       Result.FDigits := TDigits.Create(absVal); | ||||
|     Result.FIsNegative := AValue < 0; | ||||
|   end | ||||
|   else begin | ||||
|     Result.FDigits := TDigits.Create(0, 1 << 31); | ||||
|     Result.FIsNegative := True; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.FromHexadecimalString(const AValue: string): TBigInt; | ||||
| begin | ||||
|   Result := FromHexOrBinString(AValue, 16); | ||||
| end; | ||||
| 
 | ||||
| class function TBigInt.FromBinaryString(const AValue: string): TBigInt; | ||||
| begin | ||||
|   Result := FromHexOrBinString(AValue, 2); | ||||
| end; | ||||
| 
 | ||||
| { Operators } | ||||
| 
 | ||||
| operator := (const A: Int64): TBigInt; | ||||
| begin | ||||
|   Result := TBigInt.FromInt64(A); | ||||
| end; | ||||
| 
 | ||||
| operator - (const A: TBigInt): TBigInt; | ||||
| var | ||||
|   len: Integer; | ||||
| begin | ||||
|   len := Length(A.FDigits); | ||||
|   if (len > 1) or (A.FDigits[0] > 0) then | ||||
|   begin | ||||
|     Result.FDigits := Copy(A.FDigits, 0, len); | ||||
|     Result.FIsNegative := not A.FIsNegative; | ||||
|   end | ||||
|   else | ||||
|     Result := TBigInt.Zero; | ||||
| end; | ||||
| 
 | ||||
| operator + (const A, B: TBigInt): TBigInt; | ||||
| begin | ||||
|   if A.IsNegative = B.IsNegative then | ||||
|     Result := TBigInt.AddAbsoluteValues(A, B, A.IsNegative) | ||||
|   else | ||||
|     Result := TBigInt.SubtractAbsoluteValues(A, B, A.IsNegative); | ||||
| end; | ||||
| 
 | ||||
| operator - (const A, B: TBigInt): TBigInt; | ||||
| begin | ||||
|   if A.IsNegative = B.IsNegative then | ||||
|     Result := TBigInt.SubtractAbsoluteValues(A, B, A.IsNegative) | ||||
|   else | ||||
|     Result := TBigInt.AddAbsoluteValues(A, B, A.IsNegative); | ||||
| end; | ||||
| 
 | ||||
| operator * (const A, B: TBigInt): TBigInt; | ||||
| begin | ||||
|   if  (A = TBigInt.Zero) or (B = TBigInt.Zero) then | ||||
|     Result := TBigInt.Zero | ||||
|   else | ||||
|     Result := TBigInt.MultiplyAbsoluteValues(A, B, A.IsNegative <> B.IsNegative); | ||||
| end; | ||||
| 
 | ||||
| operator shl(const A: TBigInt; const B: Integer): TBigInt; | ||||
| var | ||||
|   i, j, digitShifts, bitShifts, reverseShift, len, newLength: Integer; | ||||
|   lastDigit: Cardinal; | ||||
| begin | ||||
|   // Handles shift of zero. | ||||
|   if A = 0 then | ||||
|   begin | ||||
|     Result := TBigInt.Zero; | ||||
|     Exit; | ||||
|   end; | ||||
| 
 | ||||
|   // Determines full digit shifts and bit shifts. | ||||
|   DivMod(B, CBitsPerDigit, digitShifts, bitShifts); | ||||
| 
 | ||||
|   if bitShifts > 0 then | ||||
|   begin | ||||
|     reverseShift := CBitsPerDigit - bitShifts; | ||||
|     len := Length(A.FDigits); | ||||
|     lastDigit := A.FDigits[len - 1] >> reverseShift; | ||||
|     newLength := len + digitShifts; | ||||
| 
 | ||||
|     if lastDigit = 0 then | ||||
|       SetLength(Result.FDigits, newLength) | ||||
|     else | ||||
|       SetLength(Result.FDigits, newLength + 1); | ||||
| 
 | ||||
|     // Performs full digit shifts by shifting the access index j for A.FDigits. | ||||
|     Result.FDigits[digitShifts] := A.FDigits[0] << bitShifts; | ||||
|     j := 0; | ||||
|     for i := digitShifts + 1 to newLength - 1 do | ||||
|     begin | ||||
|       // Performs bit shifts. | ||||
|       Result.FDigits[i] := A.FDigits[j] >> reverseShift; | ||||
|       Inc(j); | ||||
|       Result.FDigits[i] := Result.FDigits[i] or (A.FDigits[j] << bitShifts); | ||||
|     end; | ||||
| 
 | ||||
|     if lastDigit > 0 then | ||||
|       Result.FDigits[newLength] := lastDigit; | ||||
|   end | ||||
|   else begin | ||||
|     // Performs full digit shifts by copy if there are no bit shifts. | ||||
|     len := Length(A.FDigits); | ||||
|     SetLength(Result.FDigits, len + digitShifts); | ||||
|     for i := 0 to digitShifts - 1 do | ||||
|       Result.FDigits[i] := 0; | ||||
|     for i := 0 to len - 1 do | ||||
|       Result.FDigits[i + digitShifts] := A.FDigits[i]; | ||||
|   end; | ||||
| 
 | ||||
|   Result.FIsNegative := A.IsNegative; | ||||
| end; | ||||
| 
 | ||||
| operator shr(const A: TBigInt; const B: Integer): TBigInt; | ||||
| var | ||||
|   i, j, digitShifts, bitShifts, reverseShift, len, newLength: Integer; | ||||
|   lastDigit: Cardinal; | ||||
| begin | ||||
|   // Handles shift of zero. | ||||
|   if A = 0 then | ||||
|   begin | ||||
|     Result := TBigInt.Zero; | ||||
|     Exit; | ||||
|   end; | ||||
| 
 | ||||
|   // Determines full digit shifts and bit shifts. | ||||
|   DivMod(B, CBitsPerDigit, digitShifts, bitShifts); | ||||
| 
 | ||||
|   // Handles shift to zero. | ||||
|   if digitShifts >= Length(A.FDigits) then | ||||
|   begin | ||||
|     Result := TBigInt.Zero; | ||||
|     Exit; | ||||
|   end; | ||||
| 
 | ||||
|   if bitShifts > 0 then | ||||
|   begin | ||||
|     reverseShift := CBitsPerDigit - bitShifts; | ||||
|     len := Length(A.FDigits); | ||||
|     lastDigit := A.FDigits[len - 1] >> bitShifts; | ||||
|     newLength := len - digitShifts; | ||||
| 
 | ||||
|     if lastDigit = 0 then | ||||
|       SetLength(Result.FDigits, newLength - 1) | ||||
|     else | ||||
|       SetLength(Result.FDigits, newLength); | ||||
| 
 | ||||
|     // Performs full digit shifts by shifting the access index j for A.FDigits. | ||||
|     j := digitShifts; | ||||
|     for i := 0 to newLength - 2 do | ||||
|     begin | ||||
|       // Performs bit shifts. | ||||
|       Result.FDigits[i] := A.FDigits[j] >> bitShifts; | ||||
|       Inc(j); | ||||
|       Result.FDigits[i] := Result.FDigits[i] or (A.FDigits[j] << reverseShift); | ||||
|     end; | ||||
| 
 | ||||
|     if lastDigit > 0 then | ||||
|       Result.FDigits[newLength - 1] := lastDigit; | ||||
|   end | ||||
|   else | ||||
|     // Performs full digit shifts by copy if there are no bit shifts. | ||||
|     Result.FDigits := Copy(A.FDigits, digitShifts, Length(A.FDigits) - digitShifts); | ||||
| 
 | ||||
|   Result.FIsNegative := A.IsNegative; | ||||
| end; | ||||
| 
 | ||||
| operator = (const A, B: TBigInt): Boolean; | ||||
| begin | ||||
|   Result := A.CompareTo(B) = 0; | ||||
| end; | ||||
| 
 | ||||
| operator <> (const A, B: TBigInt): Boolean; | ||||
| begin | ||||
|   Result := A.CompareTo(B) <> 0; | ||||
| end; | ||||
| 
 | ||||
| operator < (const A, B: TBigInt): Boolean; | ||||
| begin | ||||
|   Result := A.CompareTo(B) < 0; | ||||
| end; | ||||
| 
 | ||||
| operator <= (const A, B: TBigInt): Boolean; | ||||
| begin | ||||
|   Result := A.CompareTo(B) <= 0; | ||||
| end; | ||||
| 
 | ||||
| operator > (const A, B: TBigInt): Boolean; | ||||
| begin | ||||
|   Result := A.CompareTo(B) > 0; | ||||
| end; | ||||
| 
 | ||||
| operator >= (const A, B: TBigInt): Boolean; | ||||
| begin | ||||
|   Result := A.CompareTo(B) >= 0; | ||||
| end; | ||||
| 
 | ||||
| end. | ||||
| 
 | ||||
| @ -22,7 +22,7 @@ unit UNumberTheory; | ||||
| interface | ||||
| 
 | ||||
| uses | ||||
|   Classes, SysUtils; | ||||
|   Classes, SysUtils, Generics.Collections, Math; | ||||
| 
 | ||||
| type | ||||
| 
 | ||||
| @ -34,6 +34,52 @@ type | ||||
|     class function LeastCommonMultiple(AValue1, AValue2: Int64): Int64; | ||||
|   end; | ||||
| 
 | ||||
|   TInt64Array = array of Int64; | ||||
| 
 | ||||
|   { TIntegerFactor } | ||||
| 
 | ||||
|   TIntegerFactor = record | ||||
|     Factor: Int64; | ||||
|     Exponent: Byte; | ||||
|   end; | ||||
| 
 | ||||
|   TIntegerFactors = specialize TList<TIntegerFactor>; | ||||
| 
 | ||||
|   { TIntegerFactorization } | ||||
| 
 | ||||
|   TIntegerFactorization = class | ||||
|   public | ||||
|     class function PollardsRhoAlgorithm(const AValue: Int64): TInt64Array; | ||||
|     class function GetNormalized(constref AIntegerFactorArray: TInt64Array): TIntegerFactors; | ||||
|   end; | ||||
| 
 | ||||
|   { TDividersEnumerator } | ||||
| 
 | ||||
|   TDividersEnumerator = class | ||||
|   private | ||||
|     FFactors: TIntegerFactors; | ||||
|     FCurrentExponents: array of Byte; | ||||
|     function GetCount: Integer; | ||||
|   public | ||||
|     constructor Create(constref AIntegerFactorArray: TInt64Array); | ||||
|     destructor Destroy; override; | ||||
|     function GetCurrent: Int64; | ||||
|     function MoveNext: Boolean; | ||||
|     procedure Reset; | ||||
|     property Current: Int64 read GetCurrent; | ||||
|     property Count: Integer read GetCount; | ||||
|   end; | ||||
| 
 | ||||
|   { TDividers } | ||||
| 
 | ||||
|   TDividers = class | ||||
|   private | ||||
|     FFactorArray: TInt64Array; | ||||
|   public | ||||
|     constructor Create(constref AIntegerFactorArray: TInt64Array); | ||||
|     function GetEnumerator: TDividersEnumerator; | ||||
|   end; | ||||
| 
 | ||||
| implementation | ||||
| 
 | ||||
| { TNumberTheory } | ||||
| @ -58,5 +104,177 @@ begin | ||||
|   Result := (Abs(AValue1) div GreatestCommonDivisor(AValue1, AValue2)) * Abs(AValue2); | ||||
| end; | ||||
| 
 | ||||
| { TIntegerFactorization } | ||||
| 
 | ||||
| // https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm | ||||
| class function TIntegerFactorization.PollardsRhoAlgorithm(const AValue: Int64): TInt64Array; | ||||
| var | ||||
|   primes: specialize TList<Int64>; | ||||
|   composites: specialize TStack<Int64>; | ||||
|   factor, n: Int64; | ||||
|   i: Integer; | ||||
| 
 | ||||
|   function G(const AX, AC: Int64): Int64; | ||||
|   begin | ||||
|     Result := (AX * AX + AC) mod n; | ||||
|   end; | ||||
| 
 | ||||
|   function FindFactor(const AStartValue, AC: Int64): Int64; | ||||
|   var | ||||
|     x, y, d: Int64; | ||||
|   begin | ||||
|     x := AStartValue; | ||||
|     y := x; | ||||
|     d := 1; | ||||
|     while d = 1 do | ||||
|     begin | ||||
|       x := G(x, AC); | ||||
|       y := G(G(y, AC), AC); | ||||
|       d := TNumberTheory.GreatestCommonDivisor(Abs(x - y), n); | ||||
|     end; | ||||
|     Result := d; | ||||
|   end; | ||||
| 
 | ||||
| begin | ||||
|   primes := specialize TList<Int64>.Create; | ||||
|   composites := specialize TStack<Int64>.Create; | ||||
| 
 | ||||
|   n := Abs(AValue); | ||||
|   while (n and 1) = 0 do | ||||
|   begin | ||||
|     primes.Add(2); | ||||
|     n := n shr 1; | ||||
|   end; | ||||
| 
 | ||||
|   composites.Push(n); | ||||
|   while composites.Count > 0 do | ||||
|   begin | ||||
|     n := composites.Pop; | ||||
|     i := 0; | ||||
|     repeat | ||||
|       factor := FindFactor(2 + (i + 1) div 2, 1 - i div 2); | ||||
|       if factor < n then | ||||
|       begin | ||||
|         composites.Push(factor); | ||||
|         composites.Push(n div factor); | ||||
|       end; | ||||
|       Inc(i); | ||||
|     until (factor < n) or (i > 3); | ||||
|     if factor = n then | ||||
|       primes.Add(factor); | ||||
|   end; | ||||
| 
 | ||||
|   Result := primes.ToArray; | ||||
| 
 | ||||
|   primes.Free; | ||||
|   composites.Free; | ||||
| end; | ||||
| 
 | ||||
| class function TIntegerFactorization.GetNormalized(constref AIntegerFactorArray: TInt64Array): TIntegerFactors; | ||||
| var | ||||
|   i: Integer; | ||||
|   factor: Int64; | ||||
|   normal: TIntegerFactor; | ||||
|   found: Boolean; | ||||
| begin | ||||
|   Result := TIntegerFactors.Create; | ||||
|   for factor in AIntegerFactorArray do | ||||
|   begin | ||||
|     found := False; | ||||
|     for i := 0 to Result.Count - 1 do | ||||
|       if Result[i].Factor = factor then | ||||
|       begin | ||||
|         found := True; | ||||
|         normal := Result[i]; | ||||
|         Inc(normal.Exponent); | ||||
|         Result[i] := normal; | ||||
|         Break; | ||||
|       end; | ||||
|     if not found then | ||||
|     begin | ||||
|       normal.Factor := factor; | ||||
|       normal.Exponent := 1; | ||||
|       Result.Add(normal); | ||||
|     end; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| { TDividersEnumerator } | ||||
| 
 | ||||
| function TDividersEnumerator.GetCount: Integer; | ||||
| var | ||||
|   factor: TIntegerFactor; | ||||
| begin | ||||
|   if FFactors.Count > 0 then | ||||
|   begin | ||||
|     Result := 1; | ||||
|     for factor in FFactors do | ||||
|       Result := Result * factor.Exponent; | ||||
|     Dec(Result); | ||||
|   end | ||||
|   else | ||||
|     Result := 0; | ||||
| end; | ||||
| 
 | ||||
| constructor TDividersEnumerator.Create(constref AIntegerFactorArray: TInt64Array); | ||||
| begin | ||||
|   FFactors := TIntegerFactorization.GetNormalized(AIntegerFactorArray); | ||||
|   SetLength(FCurrentExponents, FFactors.Count); | ||||
| end; | ||||
| 
 | ||||
| destructor TDividersEnumerator.Destroy; | ||||
| begin | ||||
|   FFactors.Free; | ||||
| end; | ||||
| 
 | ||||
| function TDividersEnumerator.GetCurrent: Int64; | ||||
| var | ||||
|   i: Integer; | ||||
| begin | ||||
|   Result := 1; | ||||
|   for i := Low(FCurrentExponents) to High(FCurrentExponents) do | ||||
|     if FCurrentExponents[i] > 0 then | ||||
|       Result := Result * Round(Power(FFactors[i].Factor, FCurrentExponents[i])); | ||||
| end; | ||||
| 
 | ||||
| function TDividersEnumerator.MoveNext: Boolean; | ||||
| var | ||||
|   i: Integer; | ||||
| begin | ||||
|   Result := False; | ||||
|   i := 0; | ||||
|   while (i <= High(FCurrentExponents)) and (FCurrentExponents[i] >= FFactors[i].Exponent) do | ||||
|   begin | ||||
|     FCurrentExponents[i] := 0; | ||||
|     Inc(i); | ||||
|   end; | ||||
| 
 | ||||
|   if i <= High(FCurrentExponents) then | ||||
|   begin | ||||
|     Inc(FCurrentExponents[i]); | ||||
|     Result := True; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| procedure TDividersEnumerator.Reset; | ||||
| var | ||||
|   i: Integer; | ||||
| begin | ||||
|   for i := Low(FCurrentExponents) to High(FCurrentExponents) do | ||||
|     FCurrentExponents[i] := 0; | ||||
| end; | ||||
| 
 | ||||
| { TDividers } | ||||
| 
 | ||||
| constructor TDividers.Create(constref AIntegerFactorArray: TInt64Array); | ||||
| begin | ||||
|   FFactorArray := AIntegerFactorArray; | ||||
| end; | ||||
| 
 | ||||
| function TDividers.GetEnumerator: TDividersEnumerator; | ||||
| begin | ||||
|   Result := TDividersEnumerator.Create(FFactorArray); | ||||
| end; | ||||
| 
 | ||||
| end. | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										297
									
								
								UPolynomial.pas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										297
									
								
								UPolynomial.pas
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,297 @@ | ||||
| { | ||||
|   Solutions to the Advent Of Code. | ||||
|   Copyright (C) 2024  Stefan Müller | ||||
| 
 | ||||
|   This program is free software: you can redistribute it and/or modify it under | ||||
|   the terms of the GNU General Public License as published by the Free Software | ||||
|   Foundation, either version 3 of the License, or (at your option) any later | ||||
|   version. | ||||
| 
 | ||||
|   This program is distributed in the hope that it will be useful, but WITHOUT | ||||
|   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||
|   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. | ||||
| 
 | ||||
|   You should have received a copy of the GNU General Public License along with | ||||
|   this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| } | ||||
| 
 | ||||
| unit UPolynomial; | ||||
| 
 | ||||
| {$mode ObjFPC}{$H+} | ||||
| 
 | ||||
| interface | ||||
| 
 | ||||
| uses | ||||
|   Classes, SysUtils, UBigInt; | ||||
| 
 | ||||
| type | ||||
|   TInt64Array = array of Int64; | ||||
| 
 | ||||
|   { TBigIntPolynomial } | ||||
| 
 | ||||
|   TBigIntPolynomial = object | ||||
|   private | ||||
|     FCoefficients: array of TBigInt; | ||||
|     function GetDegree: Integer; | ||||
|     function GetCoefficient(const AIndex: Integer): TBigInt; | ||||
|   public | ||||
|     property Degree: Integer read GetDegree; | ||||
|     property Coefficient[const AIndex: Integer]: TBigInt read GetCoefficient; | ||||
|     function CalcValueAt(const AX: Int64): TBigInt; | ||||
|     function CalcSignVariations: Integer; | ||||
| 
 | ||||
|     // Returns 2^n * f(x), given a polynomial f(x) and exponent n. | ||||
|     function ScaleByPowerOfTwo(const AExponent: Cardinal): TBigIntPolynomial; | ||||
| 
 | ||||
|     // Returns f(s * x), given a polynomial f(x) and scale factor s. | ||||
|     function ScaleVariable(const AScaleFactor: TBigInt): TBigIntPolynomial; | ||||
| 
 | ||||
|     // Returns f(2^n * x), given a polynomial f(x) and an exponent n. | ||||
|     function ScaleVariableByPowerOfTwo(const AExponent: Cardinal): TBigIntPolynomial; | ||||
| 
 | ||||
|     // Returns f(x / 2), given a polynomial f(x). | ||||
|     function ScaleVariableByHalf: TBigIntPolynomial; | ||||
| 
 | ||||
|     // Returns f(x + 1), given a polynomial f(x). | ||||
|     function TranslateVariableByOne: TBigIntPolynomial; | ||||
| 
 | ||||
|     // Returns a polynomial with the reverse order of coefficients, i.e. the polynomial | ||||
|     //   a_0 * x^n + a_1 * x^(n - 1) + ... + a_(n - 1) * x + a_n, | ||||
|     // given a polynomial | ||||
|     //   a_n * x^n + a_(n - 1) * x^(n - 1) + ... + a_1 * x + a_0. | ||||
|     function RevertOrderOfCoefficients: TBigIntPolynomial; | ||||
| 
 | ||||
|     // Returns a polynomial with all coefficents shifted down one position, and the constant term removed. This should | ||||
|     // only be used when the constant term is zero and is then equivalent to a division of polynomial f(x) by x. | ||||
|     function DivideByVariable: TBigIntPolynomial; | ||||
|     function IsEqualTo(const AOther: TBigIntPolynomial): Boolean; | ||||
|     function ToString: string; | ||||
|     class function Create(const ACoefficients: array of TBigInt): TBigIntPolynomial; static; | ||||
|   end; | ||||
| 
 | ||||
|   { Operators } | ||||
| 
 | ||||
|   operator = (const A, B: TBigIntPolynomial): Boolean; | ||||
|   operator <> (const A, B: TBigIntPolynomial): Boolean; | ||||
| 
 | ||||
| implementation | ||||
| 
 | ||||
| { TBigIntPolynomial } | ||||
| 
 | ||||
| function TBigIntPolynomial.GetDegree: Integer; | ||||
| begin | ||||
|   Result := Length(FCoefficients) - 1; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.GetCoefficient(const AIndex: Integer): TBigInt; | ||||
| begin | ||||
|   Result := FCoefficients[AIndex]; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.CalcValueAt(const AX: Int64): TBigInt; | ||||
| var | ||||
|   i: Integer; | ||||
| begin | ||||
|   Result := TBigInt.Zero; | ||||
|   for i := High(FCoefficients) downto 0 do | ||||
|     Result := Result * AX + FCoefficients[i]; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.CalcSignVariations: Integer; | ||||
| var | ||||
|   current, last, i: Integer; | ||||
| begin | ||||
|   Result := 0; | ||||
|   last := 0; | ||||
|   for i := 0 to Length(FCoefficients) - 1 do | ||||
|   begin | ||||
|     current := FCoefficients[i].Sign; | ||||
|     if (current <> 0) and (last <> current) then | ||||
|     begin | ||||
|       if last <> 0 then | ||||
|         Inc(Result); | ||||
|       last := current | ||||
|     end; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.ScaleByPowerOfTwo(const AExponent: Cardinal): TBigIntPolynomial; | ||||
| var | ||||
|   len, i: Integer; | ||||
| begin | ||||
|   len := Length(FCoefficients); | ||||
|   SetLength(Result.FCoefficients, len); | ||||
|   for i := 0 to len - 1 do | ||||
|     Result.FCoefficients[i] := FCoefficients[i] << AExponent; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.ScaleVariable(const AScaleFactor: TBigInt): TBigIntPolynomial; | ||||
| var | ||||
|   len, i: Integer; | ||||
|   factor: TBigInt; | ||||
| begin | ||||
|   if AScaleFactor <> TBigInt.Zero then | ||||
|   begin | ||||
|     len := Length(FCoefficients); | ||||
|     SetLength(Result.FCoefficients, len); | ||||
|     Result.FCoefficients[0] := FCoefficients[0]; | ||||
|     factor := AScaleFactor; | ||||
|     for i := 1 to len - 1 do begin | ||||
|       Result.FCoefficients[i] := FCoefficients[i] * factor; | ||||
|       factor := factor * AScaleFactor; | ||||
|     end; | ||||
|   end | ||||
|   else begin | ||||
|     SetLength(Result.FCoefficients, 1); | ||||
|     Result.FCoefficients[0] := TBigInt.Zero; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.ScaleVariableByPowerOfTwo(const AExponent: Cardinal): TBigIntPolynomial; | ||||
| var | ||||
|   len, i: Integer; | ||||
|   shift: Cardinal; | ||||
| begin | ||||
|   len := Length(FCoefficients); | ||||
|   SetLength(Result.FCoefficients, len); | ||||
|   Result.FCoefficients[0] := FCoefficients[0]; | ||||
|   shift := AExponent; | ||||
|   for i := 1 to len - 1 do begin | ||||
|     Result.FCoefficients[i] := FCoefficients[i] << shift; | ||||
|     Inc(shift, AExponent); | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.ScaleVariableByHalf: TBigIntPolynomial; | ||||
| var | ||||
|   len, i: Integer; | ||||
| begin | ||||
|     len := Length(FCoefficients); | ||||
|     SetLength(Result.FCoefficients, len); | ||||
|     Result.FCoefficients[0] := FCoefficients[0]; | ||||
|     for i := 1 to len - 1 do | ||||
|       Result.FCoefficients[i] := FCoefficients[i] >> i; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.TranslateVariableByOne: TBigIntPolynomial; | ||||
| var | ||||
|   len, i, j: Integer; | ||||
|   factors: array of Cardinal; | ||||
| begin | ||||
|   len := Length(FCoefficients); | ||||
|   SetLength(Result.FCoefficients, len); | ||||
|   SetLength(factors, len); | ||||
|   for i := 0 to len - 1 do | ||||
|   begin | ||||
|     Result.FCoefficients[i] := TBigInt.Zero; | ||||
|     factors[i] := 1; | ||||
|   end; | ||||
| 
 | ||||
|   // Calculates new coefficients. | ||||
|   for i := 0 to len - 1 do | ||||
|   begin | ||||
|     for j := 0 to len - i - 1 do | ||||
|     begin | ||||
|       if (i <> 0) and (j <> 0) then | ||||
|         factors[j] := factors[j] + factors[j - 1]; | ||||
|       Result.FCoefficients[i] := Result.FCoefficients[i] + factors[j] * FCoefficients[j + i]; | ||||
|     end; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.RevertOrderOfCoefficients: TBigIntPolynomial; | ||||
| var | ||||
|   len, skip, i: Integer; | ||||
| begin | ||||
|   // Counts the trailing zeros to skip. | ||||
|   len := Length(FCoefficients); | ||||
|   skip := 0; | ||||
|   while (skip < len) and (FCoefficients[skip] = 0) do | ||||
|     Inc(skip); | ||||
| 
 | ||||
|   // Copies the other coefficients in reverse order. | ||||
|   SetLength(Result.FCoefficients, len - skip); | ||||
|   for i := skip to len - 1 do | ||||
|     Result.FCoefficients[len - i - 1] := FCoefficients[i]; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.DivideByVariable: TBigIntPolynomial; | ||||
| var | ||||
|   len: Integer; | ||||
| begin | ||||
|   len := Length(FCoefficients); | ||||
|   if len > 1 then | ||||
|     Result.FCoefficients := Copy(FCoefficients, 1, len - 1) | ||||
|   else begin | ||||
|     SetLength(Result.FCoefficients, 1); | ||||
|     Result.FCoefficients[0] := TBigInt.Zero; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.IsEqualTo(const AOther: TBigIntPolynomial): Boolean; | ||||
| var | ||||
|   i: Integer; | ||||
| begin | ||||
|   if Length(FCoefficients) = Length(AOther.FCoefficients) then | ||||
|   begin | ||||
|     Result := True; | ||||
|     for i := 0 to Length(FCoefficients) - 1 do | ||||
|       if FCoefficients[i] <> AOther.FCoefficients[i] then | ||||
|       begin | ||||
|         Result := False; | ||||
|         Break; | ||||
|       end; | ||||
|   end | ||||
|   else | ||||
|     Result := False; | ||||
| end; | ||||
| 
 | ||||
| function TBigIntPolynomial.ToString: string; | ||||
| var | ||||
|   i: Integer; | ||||
| begin | ||||
|   Result := FCoefficients[0].ToString; | ||||
|   for i := 1 to Length(FCoefficients) - 1 do | ||||
|     if i > 1 then | ||||
|       Result := Result + ' + ' + FCoefficients[i].ToString + ' * x^' + IntToStr(i) | ||||
|     else | ||||
|       Result := Result + ' + ' + FCoefficients[i].ToString + ' * x'; | ||||
| end; | ||||
| 
 | ||||
| class function TBigIntPolynomial.Create(const ACoefficients: array of TBigInt): TBigIntPolynomial; | ||||
| var | ||||
|   high, i: integer; | ||||
| begin | ||||
|   high := -1; | ||||
|   for i := Length(ACoefficients) - 1 downto 0 do | ||||
|     if ACoefficients[i] <> 0 then | ||||
|     begin | ||||
|       high := i; | ||||
|       Break; | ||||
|     end; | ||||
|   if high >= 0 then | ||||
|   begin | ||||
|     SetLength(Result.FCoefficients, high + 1); | ||||
|     for i := 0 to high do | ||||
|       Result.FCoefficients[i] := ACoefficients[i]; | ||||
|   end | ||||
|   else begin | ||||
|     SetLength(Result.FCoefficients, 1); | ||||
|     Result.FCoefficients[0] := TBigInt.Zero; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| { Operators } | ||||
| 
 | ||||
| operator = (const A, B: TBigIntPolynomial): Boolean; | ||||
| begin | ||||
|   Result := A.IsEqualTo(B); | ||||
| end; | ||||
| 
 | ||||
| operator <> (const A, B: TBigIntPolynomial): Boolean; | ||||
| begin | ||||
|   Result := not A.IsEqualTo(B); | ||||
| end; | ||||
| 
 | ||||
| end. | ||||
| 
 | ||||
							
								
								
									
										205
									
								
								UPolynomialRoots.pas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								UPolynomialRoots.pas
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,205 @@ | ||||
| { | ||||
|   Solutions to the Advent Of Code. | ||||
|   Copyright (C) 2024  Stefan Müller | ||||
| 
 | ||||
|   This program is free software: you can redistribute it and/or modify it under | ||||
|   the terms of the GNU General Public License as published by the Free Software | ||||
|   Foundation, either version 3 of the License, or (at your option) any later | ||||
|   version. | ||||
| 
 | ||||
|   This program is distributed in the hope that it will be useful, but WITHOUT | ||||
|   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||
|   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. | ||||
| 
 | ||||
|   You should have received a copy of the GNU General Public License along with | ||||
|   this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| } | ||||
| 
 | ||||
| unit UPolynomialRoots; | ||||
| 
 | ||||
| {$mode ObjFPC}{$H+} | ||||
| 
 | ||||
| interface | ||||
| 
 | ||||
| uses | ||||
|   Classes, SysUtils, Generics.Collections, UPolynomial, UBigInt; | ||||
| 
 | ||||
| type | ||||
| 
 | ||||
|   { TIsolatingInterval } | ||||
| 
 | ||||
|   // Represents an isolating interval of the form [C / 2^K, (C + H) / 2^K] in respect to [0, 1] or [A, B] in respect to | ||||
|   // [0, 2^boundexp], with A = C * 2^boundexp / 2^K and B = (C + H) * 2^boundexp / 2^K. | ||||
|   TIsolatingInterval = record | ||||
|     C: TBigInt; | ||||
|     K, H, BoundExp: Cardinal; | ||||
|     A, B: TBigInt; | ||||
|   end; | ||||
| 
 | ||||
|   TIsolatingIntervals = specialize TList<TIsolatingInterval>; | ||||
| 
 | ||||
|   TIsolatingIntervalArray = array of TIsolatingInterval; | ||||
| 
 | ||||
|   { TPolynomialRoots } | ||||
| 
 | ||||
|   TPolynomialRoots = class | ||||
|   private | ||||
|     // Returns the exponent (base two) of an upper bound for the roots of the given polynomial, i.e. all real roots of | ||||
|     // the given polynomial are less or equal than 2^b, where b is the returned positive integer. | ||||
|     class function CalcUpperRootBound(constref APolynomial: TBigIntPolynomial): Cardinal; | ||||
|     class function CreateIsolatingInterval(constref AC: TBigInt; const AK, AH: Cardinal; constref ABoundExp: Cardinal): | ||||
|       TIsolatingInterval; | ||||
|   public | ||||
|     // Returns root-isolating intervals for non-negative, non-multiple roots. | ||||
|     class function BisectIsolation(constref APolynomial: TBigIntPolynomial): TIsolatingIntervalArray; | ||||
|     // Returns root-isolating intervals for non-multiple roots in the interval [0, 2^boundexp]. | ||||
|     class function BisectIsolation(constref APolynomial: TBigIntPolynomial; constref ABoundExp: Cardinal; | ||||
|       const AFindIntegers: Boolean = False): TIsolatingIntervalArray; | ||||
|     // Returns non-negative, non-multiple, integer roots in the interval [0, 2^boundexp]. | ||||
|     class function BisectInteger(constref APolynomial: TBigIntPolynomial; constref ABoundExp: Cardinal): | ||||
|       TBigIntArray; | ||||
|   end; | ||||
| 
 | ||||
| implementation | ||||
| 
 | ||||
| { TPolynomialRoots } | ||||
| 
 | ||||
| class function TPolynomialRoots.CalcUpperRootBound(constref APolynomial: TBigIntPolynomial): Cardinal; | ||||
| var | ||||
|   i, sign: Integer; | ||||
|   an, ai, max: TBigInt; | ||||
|   numeratorBit, denominatorBit: Int64; | ||||
| begin | ||||
|   // We need a_n > 0 here, so we use -sign(a_n) instead of actually flipping the polynomial. | ||||
|   // Sign is not 0 because a_n is not 0. | ||||
|   an := APolynomial.Coefficient[APolynomial.Degree]; | ||||
|   sign := -an.Sign; | ||||
| 
 | ||||
|   // This is a simplification of Cauchy's bound to avoid division and make it a power of two. | ||||
|   // https://en.wikipedia.org/wiki/Geometrical_properties_of_polynomial_roots#Bounds_of_positive_real_roots | ||||
|   max := TBigInt.Zero; | ||||
|   for i := 0 to APolynomial.Degree - 1 do begin | ||||
|     ai := sign * APolynomial.Coefficient[i]; | ||||
|     if max < ai then | ||||
|       max := ai; | ||||
|   end; | ||||
|   numeratorBit := max.GetMostSignificantBitIndex + 1; | ||||
|   denominatorBit := an.GetMostSignificantBitIndex; | ||||
|   Result := numeratorBit - denominatorBit; | ||||
| end; | ||||
| 
 | ||||
| class function TPolynomialRoots.CreateIsolatingInterval(constref AC: TBigInt; const AK, AH: Cardinal; | ||||
|   constref ABoundExp: Cardinal): TIsolatingInterval; | ||||
| begin | ||||
|   Result.C := AC; | ||||
|   Result.K := AK; | ||||
|   Result.H := AH; | ||||
|   Result.BoundExp := ABoundExp; | ||||
|   if ABoundExp >= AK then | ||||
|   begin | ||||
|     Result.A := AC << (ABoundExp - AK); | ||||
|     Result.B := (AC + AH) << (ABoundExp - AK); | ||||
|   end | ||||
|   else begin | ||||
|     Result.A := AC << (ABoundExp - AK); | ||||
|     Result.B := (AC + AH) << (ABoundExp - AK); | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| class function TPolynomialRoots.BisectIsolation(constref APolynomial: TBigIntPolynomial): TIsolatingIntervalArray; | ||||
| var | ||||
|   boundExp: Cardinal; | ||||
| begin | ||||
|   boundExp := CalcUpperRootBound(APolynomial); | ||||
|   Result := BisectIsolation(APolynomial, boundExp); | ||||
| end; | ||||
| 
 | ||||
| // This is adapted from https://en.wikipedia.org/wiki/Real-root_isolation#Bisection_method | ||||
| class function TPolynomialRoots.BisectIsolation(constref APolynomial: TBigIntPolynomial; constref ABoundExp: Cardinal; | ||||
|   const AFindIntegers: Boolean): TIsolatingIntervalArray; | ||||
| type | ||||
|   TWorkItem = record | ||||
|     C: TBigInt; | ||||
|     K: Cardinal; | ||||
|     P: TBigIntPolynomial; | ||||
|   end; | ||||
|   TWorkStack = specialize TStack<TWorkItem>; | ||||
| var | ||||
|   item: TWorkItem; | ||||
|   stack: TWorkStack; | ||||
|   n, v: Integer; | ||||
|   varq: TBigIntPolynomial; | ||||
|   iso: TIsolatingIntervals; | ||||
| begin | ||||
|   iso := TIsolatingIntervals.Create; | ||||
|   stack := TWorkStack.Create; | ||||
| 
 | ||||
|   item.C := 0; | ||||
|   item.K := 0; | ||||
|   item.P := APolynomial.ScaleVariableByPowerOfTwo(ABoundExp); | ||||
|   stack.Push(item); | ||||
|   n := item.P.Degree; | ||||
| 
 | ||||
|   while stack.Count > 0 do | ||||
|   begin | ||||
|     item := stack.Pop; | ||||
|     if item.P.Coefficient[0] = TBigInt.Zero then | ||||
|     begin | ||||
|       // Found an integer root at 0. | ||||
|       item.P := item.P.DivideByVariable; | ||||
|       Dec(n); | ||||
|       iso.Add(CreateIsolatingInterval(item.C, item.K, 0, ABoundExp)); | ||||
|     end; | ||||
| 
 | ||||
|     varq := item.P.RevertOrderOfCoefficients.TranslateVariableByOne; | ||||
|     v := varq.CalcSignVariations; | ||||
|     if (v > 1) | ||||
|     or ((v = 1) and AFindIntegers and (item.K < ABoundExp)) then | ||||
|     begin | ||||
|       // Bisects, first new work item is (2c, k + 1, 2^n * q(x/2)). | ||||
|       item.C := item.C << 1; | ||||
|       Inc(item.K); | ||||
|       item.P := item.P.ScaleVariableByHalf.ScaleByPowerOfTwo(n); | ||||
|       stack.Push(item); | ||||
|       // ... second new work item is (2c + 1, k + 1, 2^n * q((x+1)/2)). | ||||
|       item.C := item.C + 1; | ||||
|       item.P := item.P.TranslateVariableByOne; | ||||
|       stack.Push(item); | ||||
|     end | ||||
|     else if v = 1 then | ||||
|     begin | ||||
|       // Found isolating interval. | ||||
|       iso.Add(CreateIsolatingInterval(item.C, item.K, 1, ABoundExp)); | ||||
|     end; | ||||
|   end; | ||||
|   Result := iso.ToArray; | ||||
|   iso.Free; | ||||
|   stack.Free; | ||||
| end; | ||||
| 
 | ||||
| class function TPolynomialRoots.BisectInteger(constref APolynomial: TBigIntPolynomial; constref ABoundExp: Cardinal): | ||||
|   TBigIntArray; | ||||
| var | ||||
|   intervals: TIsolatingIntervalArray; | ||||
|   i: TIsolatingInterval; | ||||
|   r: specialize TList<TBigInt>; | ||||
|   value: Int64; | ||||
| begin | ||||
|   // Calculates isolating intervals. | ||||
|   intervals := BisectIsolation(APolynomial, ABoundExp, True); | ||||
|   r := specialize TList<TBigInt>.Create; | ||||
| 
 | ||||
|   for i in intervals do | ||||
|     if i.H = 0 then | ||||
|       r.Add(i.A) | ||||
|     else if i.A.TryToInt64(value) and (APolynomial.CalcValueAt(value) = 0) then | ||||
|       r.Add(value) | ||||
|     else if i.B.TryToInt64(value) and (APolynomial.CalcValueAt(value) = 0) then | ||||
|       r.Add(value); | ||||
| 
 | ||||
|   Result := r.ToArray; | ||||
|   r.Free; | ||||
| end; | ||||
| 
 | ||||
| end. | ||||
| 
 | ||||
| @ -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,26 +22,37 @@ unit UNeverTellMeTheOdds; | ||||
| interface | ||||
| 
 | ||||
| uses | ||||
|   Classes, SysUtils, Generics.Collections, Math, USolver; | ||||
|   Classes, SysUtils, Generics.Collections, Math, USolver, UNumberTheory, UBigInt, UPolynomial, UPolynomialRoots; | ||||
| 
 | ||||
| type | ||||
| 
 | ||||
|   { THailstone } | ||||
| 
 | ||||
|   THailstone = record | ||||
|     X, Y, Z: Int64; | ||||
|     VX, VY, VZ: Integer; | ||||
|   THailstone = class | ||||
|   public | ||||
|     P0, P1, P2: Int64; | ||||
|     V0, V1, V2: Integer; | ||||
|     constructor Create(const ALine: string); | ||||
|     constructor Create; | ||||
|   end; | ||||
| 
 | ||||
|   THailstones = specialize TList<THailstone>; | ||||
|   THailstones = specialize TObjectList<THailstone>; | ||||
| 
 | ||||
|   TInt64Array = array of Int64; | ||||
| 
 | ||||
|   { TNeverTellMeTheOdds } | ||||
| 
 | ||||
|   TNeverTellMeTheOdds = class(TSolver) | ||||
|   private | ||||
|     FMin, FMax: Int64; | ||||
|     FHailStones: THailstones; | ||||
|     FHailstones: THailstones; | ||||
|     function AreIntersecting(constref AHailstone1, AHailstone2: THailstone): Boolean; | ||||
|     function FindRockThrow(const AIndex0, AIndex1, AIndex2: Integer): Int64; | ||||
|     procedure CalcCollisionPolynomials(constref AHailstone0, AHailstone1, AHailstone2: THailstone; out OPolynomial0, | ||||
|       OPolynomial1: TBigIntPolynomial); | ||||
|     function CalcRockThrowCollisionOptions(constref AHailstone0, AHailstone1, AHailstone2: THailstone): TInt64Array; | ||||
|     function ValidateRockThrow(constref AHailstone0, AHailstone1, AHailstone2: THailstone; const AT0, AT1: Int64): | ||||
|       Int64; | ||||
|   public | ||||
|     constructor Create(const AMin: Int64 = 200000000000000; const AMax: Int64 = 400000000000000); | ||||
|     destructor Destroy; override; | ||||
| @ -53,6 +64,26 @@ type | ||||
| 
 | ||||
| implementation | ||||
| 
 | ||||
| { THailstone } | ||||
| 
 | ||||
| constructor THailstone.Create(const ALine: string); | ||||
| var | ||||
|   split: TStringArray; | ||||
| begin | ||||
|   split := ALine.Split([',', '@']); | ||||
|   P0 := StrToInt64(Trim(split[0])); | ||||
|   P1 := StrToInt64(Trim(split[1])); | ||||
|   P2 := StrToInt64(Trim(split[2])); | ||||
|   V0 := StrToInt(Trim(split[3])); | ||||
|   V1 := StrToInt(Trim(split[4])); | ||||
|   V2 := StrToInt(Trim(split[5])); | ||||
| end; | ||||
| 
 | ||||
| constructor THailstone.Create; | ||||
| begin | ||||
| 
 | ||||
| end; | ||||
| 
 | ||||
| { TNeverTellMeTheOdds } | ||||
| 
 | ||||
| function TNeverTellMeTheOdds.AreIntersecting(constref AHailstone1, AHailstone2: THailstone): Boolean; | ||||
| @ -60,58 +91,432 @@ var | ||||
|   m1, m2, x, y: Double; | ||||
| begin | ||||
|   Result := False; | ||||
|   m1 := AHailstone1.VY / AHailstone1.VX; | ||||
|   m2 := AHailstone2.VY / AHailstone2.VX; | ||||
|   m1 := AHailstone1.V1 / AHailstone1.V0; | ||||
|   m2 := AHailstone2.V1 / AHailstone2.V0; | ||||
|   if m1 <> m2 then | ||||
|   begin | ||||
|     x := (AHailstone2.Y - m2 * AHailstone2.X - AHailstone1.Y + m1 * AHailstone1.X) / (m1 - m2); | ||||
|     x := (AHailstone2.P1 - m2 * AHailstone2.P0 | ||||
|       - AHailstone1.P1 + m1 * AHailstone1.P0) | ||||
|       / (m1 - m2); | ||||
|     if (FMin <= x) and (x <= FMax) | ||||
|     and (x * Sign(AHailstone1.VX) >= AHailstone1.X * Sign(AHailstone1.VX)) | ||||
|     and (x * Sign(AHailstone2.VX) >= AHailstone2.X * Sign(AHailstone2.VX)) then | ||||
|     and (x * Sign(AHailstone1.V0) >= AHailstone1.P0 * Sign(AHailstone1.V0)) | ||||
|     and (x * Sign(AHailstone2.V0) >= AHailstone2.P0 * Sign(AHailstone2.V0)) | ||||
|     then | ||||
|     begin | ||||
|       y := m1 * (x - AHailstone1.X) + AHailstone1.Y; | ||||
|       y := m1 * (x - AHailstone1.P0) + AHailstone1.P1; | ||||
|       if (FMin <= y) and (y <= FMax) then | ||||
|         Result := True | ||||
|     end; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| function TNeverTellMeTheOdds.FindRockThrow(const AIndex0, AIndex1, AIndex2: Integer): Int64; | ||||
| var | ||||
|   t0, t1: TInt64Array; | ||||
|   i, j: Int64; | ||||
| begin | ||||
|   t0 := CalcRockThrowCollisionOptions(FHailstones[AIndex0], FHailstones[AIndex1], FHailstones[AIndex2]); | ||||
|   t1 := CalcRockThrowCollisionOptions(FHailstones[AIndex1], FHailstones[AIndex0], FHailstones[AIndex2]); | ||||
| 
 | ||||
|   Result := 0; | ||||
|   for i in t0 do | ||||
|   begin | ||||
|     for j in t1 do | ||||
|     begin | ||||
|       Result := ValidateRockThrow(FHailstones[AIndex0], FHailstones[AIndex1], FHailstones[AIndex2], i, j); | ||||
|       if Result > 0 then | ||||
|         Break; | ||||
|     end; | ||||
|     if Result > 0 then | ||||
|       Break; | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| procedure TNeverTellMeTheOdds.CalcCollisionPolynomials(constref AHailstone0, AHailstone1, AHailstone2: THailstone; out | ||||
|   OPolynomial0, OPolynomial1: TBigIntPolynomial); | ||||
| var | ||||
|   k: array[0..74] of TBigInt; | ||||
| begin | ||||
|   // Solving this non-linear equation system, with velocities V_i and start positions P_i: | ||||
|   //   V_0 * t_0 + P_0 = V_x * t_0 + P_x | ||||
|   //   V_1 * t_1 + P_1 = V_x * t_1 + P_x | ||||
|   //   V_2 * t_2 + P_2 = V_x * t_2 + P_x | ||||
|   // Which gives: | ||||
|   //   P_x = (V_0 - V_x) * t_0 + P_0 | ||||
|   //   V_x = (V_0 * t_0 - V_1 * t_1 + P_0 - P_1) / (t_0 - t_1) | ||||
|   // And with vertex components: | ||||
|   //   1:  0 = (t_1 - t_0) * (V_00 * t_0 - V_20 * t_2 + P_00 - P_20) | ||||
|   //          - (t_2 - t_0) * (V_00 * t_0 - V_10 * t_1 + P_00 - P_10) | ||||
|   //   2:  t_1 = (((V_01 - V_21) * t_2 + P_11 - P_21) * t_0 + (P_01 - P_11) * t_2) | ||||
|   //            / ((V_01 - V_11) * t_0 + (V_11 - V_21) * t_2 + P_01 - P_21) | ||||
|   //   3:  t_2 = (((V_02 - V_12) * t_1 + P_22 - P_12) * t_0 + (P_02 - P_22) * t_1) | ||||
|   //            / ((V_02 - V_22) * t_0 + (V_22 - V_12) * t_1 + P_02 - P_12) | ||||
|   //   for t_0, t_1, t_2 not pairwise equal. | ||||
|   // With some substitutions depending only on t_0 this gives | ||||
|   //   1:  0 = (t_1 - t_0) * (a_1 - V_20 * t_2) - (t_2 - t_0) * (a_2 - V_10 * t_1) | ||||
|   //   2:  t_1 = (b_0 + b_1 * t_2) / (c_0 + c_1 * t_2) | ||||
|   //   3:  t_2 = (d_0 + d_1 * t_1) / (e_0 + e_1 * t_1) | ||||
|   // And 3 in 2 gives: | ||||
|   //   4:  f_2 * t_1^2 + f_1 * t_1 - f_0 = 0 | ||||
|   // Then, with 4 and 3 in 1 and many substitutions (see constants k below, now independent of t_0), the equation | ||||
|   //   5:  0 = p_0(t_0) + p_1(t_0) * sqrt(p_2(t_0)) | ||||
|   // can be constructed, where p_0, p_1, and p_2 are polynomials in t_0. Since we are searching for an integer solution, | ||||
|   // we assume that there is an integer t_0 that is a root of both p_0 and p_1, which would solve the equation. | ||||
| 
 | ||||
|   // Subsitutions depending on t_0: | ||||
|   //   a_1 = V_00 * t_0 + P_00 - P_20 | ||||
|   //   a_2 = V_00 * t_0 + P_00 - P_10 | ||||
|   //   b_0 = (P_11 - P_21) * t_0 | ||||
|   //   b_1 = (V_01 - V_21) * t_0 + P_01 - P_11 | ||||
|   //   c_0 = (V_01 - V_11) * t_0 + P_01 - P_21 | ||||
|   //   c_1 = V_11 - V_21 | ||||
|   //   d_0 = (P_22 - P_12) * t_0 | ||||
|   //   d_1 = (V_02 - V_12) * t_0 + P_02 - P_22 | ||||
|   //   e_0 = (V_02 - V_22) * t_0 + P_02 - P_12 | ||||
|   //   e_1 = V_22 - V_12 | ||||
|   //   f_0 = b_1 * d_0 + b_0 * e_0 | ||||
|   //   f_1 = c_0 * e_0 + c_1 * d_0 - b_0 * e_1 - b_1 * d_1 | ||||
|   //   f_2 = c_0 * e_1 + c_1 * d_1 | ||||
| 
 | ||||
|   // Calculations for equation 5 (4 and 3 in 1). | ||||
|   // 1:  0 = (t_1 - t_0) * (a_1 - V_20 * t_2) - (t_2 - t_0) * (a_2 - V_10 * t_1) | ||||
|   // 3:  (e_0 + e_1 * t_1) * t_2 = (d_0 + d_1 * t_1) | ||||
|   // 0 = (t_1 - t_0) * (a_1 - V_20 * t_2) - (t_2 - t_0) * (a_2 - V_10 * t_1) | ||||
|   //   = (t_1 - t_0) * (a_1 * (e_0 + e_1 * t_1) - V_20 * (e_0 + e_1 * t_1) * t_2) - ((e_0 + e_1 * t_1) * t_2 - (e_0 + e_1 * t_1) * t_0) * (a_2 - V_10 * t_1) | ||||
|   //   = (t_1 - t_0) * (a_1 * (e_0 + e_1 * t_1) - V_20 * (d_0 + d_1 * t_1)) - ((d_0 + d_1 * t_1) - (e_0 + e_1 * t_1) * t_0) * (a_2 - V_10 * t_1) | ||||
|   //   = (t_1 - t_0) * (a_1 * e_0 + a_1 * e_1 * t_1 - V_20 * d_0 - V_20 * d_1 * t_1) - (d_0 + d_1 * t_1 - e_0 * t_0 - e_1 * t_1 * t_0) * (a_2 - V_10 * t_1) | ||||
|   //   = (a_1 * e_1 - V_20 * d_1) * t_1^2 + (a_1 * e_0 - V_20 * d_0 - t_0 * (a_1 * e_1  - V_20 * d_1)) * t_1 - t_0 * (a_1 * e_0 - V_20 * d_0) | ||||
|   //     - ( - V_10 * (d_1 - e_1 * t_0) * t_1^2 + ((d_1 - e_1 * t_0) * a_2 - V_10 * (d_0 - e_0 * t_0)) * t_1 + (d_0 - e_0 * t_0) * a_2) | ||||
|   //   = (a_1 * e_1 - V_20 * d_1 + V_10 * (d_1 - e_1 * t_0)) * t_1^2 | ||||
|   //     + (a_1 * e_0 - V_20 * d_0 - t_0 * (a_1 * e_1  - V_20 * d_1) - (d_1 - e_1 * t_0) * a_2 + V_10 * (d_0 - e_0 * t_0)) * t_1 | ||||
|   //     + t_0 * (V_20 * d_0 - a_1 * e_0) + (e_0 * t_0 - d_0) * a_2 | ||||
|   // Inserting 4, solved for t_0:  t_1 = - f_1 / (2 * f_2) + sqrt((f_1 / (2 * f_2))^2 + f_0 / f_2) | ||||
|   //   = (a_1 * e_1 - V_20 * d_1 + V_10 * (d_1 - e_1 * t_0)) * (f_1^2 + 2 * f_0 * f_2 - f_1 * sqrt(f_1^2 + 4 * f_0 * f_2)) | ||||
|   //     + (a_1 * e_0 - V_20 * d_0 - t_0 * (a_1 * e_1 - V_20 * d_1) - (d_1 - e_1 * t_0) * a_2 + V_10 * (d_0 - e_0 * t_0)) * (- f_1 * f_2 + f_2 * sqrt(f_1^2 + 4 * f_0 * f_2)) | ||||
|   //     + t_0 * (V_20 * d_0 - a_1 * e_0) * 2 * f_2^2 + (e_0 * t_0 - d_0) * a_2 * 2 * f_2^2 | ||||
| 
 | ||||
|   // a_1 = V_00 * t_0 + k_0 | ||||
|   // a_2 = V_00 * t_0 + k_1 | ||||
|   // b_0 = k_2 * t_0 | ||||
|   // b_1 = k_10 * t_0 + k_3 | ||||
|   // c_0 = k_11 * t_0 + k_4 | ||||
|   // d_0 = k_5 * t_0 | ||||
|   // d_1 = k_12 * t_0 + k_6 | ||||
|   // e_0 = k_13 * t_0 + k_7 | ||||
|   // f_2 = (k_11 * t_0 + k_4) * k_9 + k_8 * (k_12 * t_0 + k_6) | ||||
|   //     = (k_11 * k_9 + k_8 * k_12) * t_0 + k_4 * k_9 + k_8 * k_6 | ||||
|   //     = k_14 * t_0 + k_15 | ||||
|   // f_1 = (k_11 * t_0 + k_4) * (k_13 * t_0 + k_7) + k_8 * k_5 * t_0 - k_2 * t_0 * k_9 - (k_10 * t_0 + k_3) * (k_12 * t_0 + k_6) | ||||
|   //     = (k_11 * k_13 - k_10 * k_12) * t_0^2 + (k_11 * k_7 + k_4 * k_12 + k_8 * k_5 - k_2 * k_9 - k_10 * k_6 - k_3 * k_12) * t_0 + k_4 * k_7 - k_3 * k_6 | ||||
|   //     = k_16 * t_0^2 + k_17 * t_0 + k_18 | ||||
|   // f_0 = (k_10 * t_0 + k_3) * k_5 * t_0 + k_2 * t_0 * (k_13 * t_0 + k_7) | ||||
|   //     = (k_10 * k_5 + k_2 * k_13) * t_0^2 + (k_3 * k_5 + k_2 * k_7) * t_0 | ||||
|   //     = k_19 * t_0^2 + k_20 * t_0 | ||||
| 
 | ||||
|   k[0] := AHailstone0.P0 - AHailstone2.P0; | ||||
|   k[1] := AHailstone0.P0 - AHailstone1.P0; | ||||
|   k[2] := AHailstone1.P1 - AHailstone2.P1; | ||||
|   k[3] := AHailstone0.P1 - AHailstone1.P1; | ||||
|   k[4] := AHailstone0.P1 - AHailstone2.P1; | ||||
|   k[5] := AHailstone2.P2 - AHailstone1.P2; | ||||
|   k[6] := AHailstone0.P2 - AHailstone2.P2; | ||||
|   k[7] := AHailstone0.P2 - AHailstone1.P2; | ||||
|   k[8] := AHailstone1.V1 - AHailstone2.V1; | ||||
|   k[9] := AHailstone2.V2 - AHailstone1.V2; | ||||
|   k[10] := AHailstone0.V1 - AHailstone2.V1; | ||||
|   k[11] := AHailstone0.V1 - AHailstone1.V1; | ||||
|   k[12] := AHailstone0.V2 - AHailstone1.V2; | ||||
|   k[13] := AHailstone0.V2 - AHailstone2.V2; | ||||
| 
 | ||||
|   k[14] := k[11] * k[9] + k[8] * k[12]; | ||||
|   k[15] := k[4] * k[9] + k[8] * k[6]; | ||||
|   k[16] := k[11] * k[13] - k[10] * k[12]; | ||||
|   k[17] := k[11] * k[7] + k[4] * k[13] + k[8] * k[5] - k[2] * k[9] - k[10] * k[6] - k[3] * k[12]; | ||||
|   k[18] := k[4] * k[7] - k[3] * k[6]; | ||||
|   k[19] := k[10] * k[5] + k[2] * k[13]; | ||||
|   k[20] := k[3] * k[5] + k[2] * k[7]; | ||||
| 
 | ||||
|   // Additional substitutions. | ||||
|   //   a_1 * k_9 - V_20 * d_1 | ||||
|   //       = (V_00 * t_0 + k_0) * k_9 - V_20 * (k_12 * t_0 + k_6) | ||||
|   //       = (V_00 * k_9 - V_20 * k_12) * t_0 + k_0 * k_9 - V_20 * k_6 | ||||
|   //       = k_21 * t_0 + k_22 | ||||
|   //   d_1 - k_9 * t_0 | ||||
|   //       = k_12 * t_0 + k_6 - k_9 * t_0 | ||||
|   //       = (k_12 - k_9) * t_0 + k_6 | ||||
|   //   a_1 * e_0 - V_20 * d_0 | ||||
|   //       = (V_00 * t_0 + k_0) * (k_13 * t_0 + k_7) - V_20 * k_5 * t_0 | ||||
|   //       = V_00 * k_13 * t_0^2 + (V_00 * k_7 + k_0 * k_13 - V_20 * k_5) * t_0 + k_0 * k_7 | ||||
|   //       = k_23 * t_0^2 + k_24 * t_0 + k_25 | ||||
|   //   d_0 - e_0 * t_0 | ||||
|   //       = k_5 * t_0 - k_13 * t_0^2 - k_7 * t_0 | ||||
|   //       = - k_13 * t_0^2 + k_26 * t_0 | ||||
|   //   f_1^2 | ||||
|   //       = (k_16 * t_0^2 + k_17 * t_0 + k_18)^2 | ||||
|   //       = k_16^2 * t_0^4 + k_17^2 * t_0^2 + k_18^2 + 2 * k_16 * t_0^2 * k_17 * t_0 + 2 * k_16 * t_0^2 * k_18 + 2 * k_17 * t_0 * k_18 | ||||
|   //       = k_16^2 * t_0^4 + 2 * k_16 * k_17 * t_0^3 + (k_17^2 + 2 * k_16 * k_18) * t_0^2 + 2 * k_17 * k_18 * t_0 + k_18^2 | ||||
|   //       = k_16^2 * t_0^4 + k_27 * t_0^3 + k_29 * t_0^2 + k_30 * t_0 + k_18^2 | ||||
|   //   f_2^2 | ||||
|   //       = (k_14 * t_0 + k_15)^2 | ||||
|   //       = k_14^2 * t_0^2 + 2 * k_14 * k_15 * t_0 + k_15^2 | ||||
|   //       = k_14^2 * t_0^2 + k_31 * t_0 + k_15^2 | ||||
|   //   f_0 * f_2 | ||||
|   //       = (k_19 * t_0^2 + k_20 * t_0) * (k_14 * t_0 + k_15) | ||||
|   //       = k_19 * k_14 * t_0^3 + (k_19 * k_15 + k_20 * k_14) * t_0^2 + k_20 * k_15 * t_0 | ||||
|   //       = k_33 * t_0^3 + k_34 * t_0^2 + k_35 * t_0 | ||||
|   //   f_1^2 + 4 * f_0 * f_2 | ||||
|   //       = k_16^2 * t_0^4 + k_27 * t_0^3 + k_29 * t_0^2 + k_30 * t_0 + k_18^2 + 4 * (k_33 * t_0^3 + k_34 * t_0^2 + k_35 * t_0) | ||||
|   //       = k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59 | ||||
|   //   f_1^2 + 2 * f_0 * f_2 | ||||
|   //       = k_16^2 * t_0^4 + k_27 * t_0^3 + k_29 * t_0^2 + k_30 * t_0 + k_18^2 + 2 * (k_33 * t_0^3 + k_34 * t_0^2 + k_35 * t_0) | ||||
|   //       = k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59 | ||||
| 
 | ||||
|   k[21] := AHailstone0.V0 * k[9] - AHailstone2.V0 * k[12]; | ||||
|   k[22] := k[0] * k[9] - AHailstone2.V0 * k[6]; | ||||
|   k[23] := AHailstone0.V0 * k[13]; | ||||
|   k[24] := AHailstone0.V0 * k[7] + k[0] * k[13] - AHailstone2.V0 * k[5]; | ||||
|   k[25] := k[0] * k[7]; | ||||
|   k[26] := k[5] - k[7]; | ||||
|   k[27] := 2 * k[16] * k[17]; | ||||
|   k[28] := k[17] * k[17]; | ||||
|   k[29] := k[28] + 2 * k[16] * k[18]; | ||||
|   k[30] := 2 * k[17] * k[18]; | ||||
|   k[31] := 2 * k[14] * k[15]; | ||||
|   k[32] := k[14] * k[14]; | ||||
|   k[33] := k[19] * k[14]; | ||||
|   k[34] := k[19] * k[15] + k[20] * k[14]; | ||||
|   k[35] := k[20] * k[15]; | ||||
|   k[36] := k[15] * k[15]; | ||||
|   k[37] := k[16] * k[16]; | ||||
|   k[38] := k[27] + 2 * k[33]; | ||||
|   k[39] := k[29] + 2 * k[34]; | ||||
|   k[40] := k[30] + 2 * k[35]; | ||||
|   k[41] := k[21] + AHailstone1.V0 * (k[12] - k[9]); | ||||
|   k[42] := k[22] + AHailstone1.V0 * k[6]; | ||||
|   k[43] := k[23] - k[21] - AHailstone1.V0 * k[13] - (k[12] - k[9]) * AHailstone0.V0; | ||||
|   k[44] := k[24] - k[22] + AHailstone1.V0 * k[26] - (k[12] - k[9]) * k[1] - k[6] * AHailstone0.V0; | ||||
|   k[45] := k[25] - k[6] * k[1]; | ||||
|   k[46] := k[43] * k[14] - k[41] * k[16]; | ||||
|   k[47] := k[43] * k[15] + k[44] * k[14] - k[42] * k[16] - k[41] * k[17]; | ||||
|   k[48] := k[44] * k[15] + k[45] * k[14] - k[42] * k[17] - k[41] * k[18]; | ||||
|   k[49] := k[45] * k[15] - k[42] * k[18]; | ||||
|   k[50] := k[43] * k[16]; | ||||
|   k[51] := k[41] * k[37] - k[50] * k[14]; | ||||
|   k[52] := k[43] * k[17] + k[44] * k[16]; | ||||
|   k[53] := k[41] * k[38] + k[42] * k[37] - k[52] * k[14] - k[50] * k[15]; | ||||
|   k[54] := k[43] * k[18] + k[44] * k[17] + k[45] * k[16]; | ||||
|   k[55] := k[41] * k[39] + k[42] * k[38] - k[54] * k[14] - k[52] * k[15]; | ||||
|   k[56] := k[44] * k[18] + k[45] * k[17]; | ||||
|   k[57] := k[41] * k[40] + k[42] * k[39] - k[56] * k[14] - k[54] * k[15]; | ||||
|   k[58] := k[45] * k[18]; | ||||
|   k[59] := k[18] * k[18]; | ||||
|   k[60] := k[41] * k[59] + k[42] * k[40] - k[58] * k[14] - k[56] * k[15]; | ||||
|   k[61] := k[42] * k[59] - k[58] * k[15]; | ||||
|   k[62] := k[13] * AHailstone0.V0 - k[23]; | ||||
|   k[63] := 2 * k[32] * k[62]; | ||||
|   k[64] := k[13] * k[1] - k[26] * AHailstone0.V0 - k[24]; | ||||
|   k[65] := 2 * (k[31] * k[62] + k[32] * k[64]); | ||||
|   k[66] := - k[26] * k[1] - k[25]; | ||||
|   k[67] := 2 * (k[36] * k[62] + k[31] * k[64] + k[32] * k[66]); | ||||
|   k[68] := 2 * (k[36] * k[64] + k[31] * k[66]); | ||||
|   k[69] := 2 * k[36] * k[66]; | ||||
|   k[70] := k[51] + k[63]; | ||||
|   k[71] := k[53] + k[65]; | ||||
|   k[72] := k[55] + k[67]; | ||||
|   k[73] := k[57] + k[68]; | ||||
|   k[74] := k[60] + k[69]; | ||||
|   // Unused, they are part of the polynomial inside the square root. | ||||
|   //k[75] := k[27] + 4 * k[33]; | ||||
|   //k[76] := k[29] + 4 * k[34]; | ||||
|   //k[77] := k[30] + 4 * k[35]; | ||||
| 
 | ||||
|   // Continuing calculations for equation 5. | ||||
|   // 0 = (k_21 * t_0 + k_22 + V_10 * ((k_12 - k_9) * t_0 + k_6)) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59 -+ f_1 * sqrt(f_1^2 + 4 * f_0 * f_2)) | ||||
|   //     + (k_23 * t_0^2 + k_24 * t_0 + k_25 - t_0 * (k_21 * t_0 + k_22) - ((k_12 - k_9) * t_0 + k_6) * a_2 - V_10 * (k_13 * t_0^2 - k_26 * t_0)) * (- f_1 * f_2 +- f_2 * sqrt(f_1^2 + 4 * f_0 * f_2)) | ||||
|   //     - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) | ||||
|   // 0 = (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59 -+ f_1 * sqrt(f_1^2 + 4 * f_0 * f_2)) | ||||
|   //     + ((k_23 - k_21 - V_10 * k_13 - (k_12 - k_9) * V_00) * t_0^2 + (k_24 - k_22 + V_10 * k_26 - (k_12 - k_9) * k_1 - k_6 * V_00) * t_0 + k_25 - k_6 * k_1) * (- f_1 * f_2 +- f_2 * sqrt(f_1^2 + 4 * f_0 * f_2)) | ||||
|   //     - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) | ||||
|   // 0 = (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59) | ||||
|   //     -+ (k_41 * t_0 + k_42) * f_1 * sqrt(f_1^2 + 4 * f_0 * f_2) | ||||
|   //     + (k_43 * t_0^2 + k_44 * t_0 + k_45) * (- f_1 * f_2 +- f_2 * sqrt(f_1^2 + 4 * f_0 * f_2)) | ||||
|   //     - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) | ||||
|   // 0 = (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59) | ||||
|   //     -+ (k_41 * t_0 + k_42) * f_1 * sqrt(f_1^2 + 4 * f_0 * f_2) | ||||
|   //     - (k_43 * t_0^2 + k_44 * t_0 + k_45) * f_1 * f_2 | ||||
|   //     +- (k_43 * t_0^2 + k_44 * t_0 + k_45) * f_2 * sqrt(f_1^2 + 4 * f_0 * f_2) | ||||
|   //     - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) | ||||
|   // 0 = +- ((k_43 * t_0^2 + k_44 * t_0 + k_45) * f_2 - (k_41 * t_0 + k_42) * f_1) * sqrt(f_1^2 + 4 * f_0 * f_2) | ||||
|   //     + (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59) | ||||
|   //     - (k_43 * t_0^2 + k_44 * t_0 + k_45) * f_1 * f_2 | ||||
|   //     - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) | ||||
|   // 0 = +- ((k_43 * t_0^2 + k_44 * t_0 + k_45) * (k_14 * t_0 + k_15) - (k_41 * t_0 + k_42) * (k_16 * t_0^2 + k_17 * t_0 + k_18)) * sqrt(f_1^2 + 4 * f_0 * f_2) | ||||
|   //     + (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59) | ||||
|   //     - (k_43 * t_0^2 + k_44 * t_0 + k_45) * (k_16 * t_0^2 + k_17 * t_0 + k_18) * (k_14 * t_0 + k_15) | ||||
|   //     - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * (V_00 * t_0 + k_1) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) | ||||
|   // 0 = +- ( | ||||
|   //         (k_43 * k_14 - k_41 * k_16) * t_0^3 | ||||
|   //         + (k_43 * k_15 + k_44 * k_14 - k_42 * k_16 - k_41 * k_17) * t_0^2 | ||||
|   //         + (k_44 * k_15 + k_45 * k_14 - k_42 * k_17 - k_41 * k_18) * t_0 | ||||
|   //         + k_45 * k_15 - k_42 * k_18 | ||||
|   //     ) * sqrt(f_1^2 + 4 * f_0 * f_2) | ||||
|   //     + (k_41 * k_37 - k_43 * k_16 * k_14) * t_0^5 | ||||
|   //     + (k_41 * k_38 + k_42 * k_37 - k_43 * k_17 * k_14 - k_44 * k_16 * k_14 - k_43 * k_16 * k_15) * t_0^4 | ||||
|   //     + (k_41 * k_39 + k_42 * k_38 - k_43 * k_18 * k_14 - k_44 * k_17 * k_14 - k_45 * k_16 * k_14 - k_43 * k_17 * k_15 - k_44 * k_16 * k_15) * t_0^3 | ||||
|   //     + (k_41 * k_40 + k_42 * k_39 - k_44 * k_18 * k_14 - k_45 * k_17 * k_14 - k_43 * k_18 * k_15 - k_44 * k_17 * k_15 - k_45 * k_16 * k_15) * t_0^2 | ||||
|   //     + (k_41 * k_59 + k_42 * k_40 - k_45 * k_18 * k_14 - k_44 * k_18 * k_15 - k_45 * k_17 * k_15) * t_0 | ||||
|   //     + k_42 * k_59 - k_45 * k_18 * k_15 | ||||
|   //     + 2 * (k_13 * V_00 * k_14^2 - k_23 * k_14^2) * t_0^5 | ||||
|   //     + 2 * (k_13 * V_00 * k_31 + k_13 * k_1 * k_14^2 - k_26 * V_00 * k_14^2 - k_23 * k_31 - k_24 * k_14^2) * t_0^4 | ||||
|   //     + 2 * (k_13 * V_00 * k_15^2 + k_13 * k_1 * k_31 - k_26 * V_00 * k_31 - k_26 * k_1 * k_14^2 - k_23 * k_15^2 - k_24 * k_31 - k_25 * k_14^2) * t_0^3 | ||||
|   //     + 2 * (k_13 * k_1 * k_15^2 - k_26 * V_00 * k_15^2 - k_26 * k_1 * k_31 - k_24 * k_15^2 - k_25 * k_31) * t_0^2 | ||||
|   //     + 2 * (- k_26 * k_1 * k_15^2 - k_25 * k_15^2) * t_0 | ||||
|   // 0 = +- (k_46 * t_0^3 + k_47 * t_0^2 + k_48 * t_0 + k_49) * sqrt(f_1^2 + 4 * f_0 * f_2) | ||||
|   //     + (k_51 + k_63) * t_0^5 + (k_53 + k_65) * t_0^4 + (k_55 + k_67) * t_0^3 + (k_57 + k_68) * t_0^2 + (k_60 + k_69) * t_0 + k_61 | ||||
|   // 0 = +- (k_46 * t_0^3 + k_47 * t_0^2 + k_48 * t_0 + k_49) * sqrt(k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59) | ||||
|   //     + k_70 * t_0^5 + k_71 * t_0^4 + k_72 * t_0^3 + k_73 * t_0^2 + k_74 * t_0 + k_61 | ||||
| 
 | ||||
|   OPolynomial0 := TBigIntPolynomial.Create([k[61], k[74], k[73], k[72], k[71], k[70]]); | ||||
|   OPolynomial1 := TBigIntPolynomial.Create([k[49], k[48], k[47], k[46]]); | ||||
| 
 | ||||
|   // Squaring that formula eliminates the square root, but may lead to a polynomial with all coefficients zero in some | ||||
|   // cases. Therefore this part is merely included for the interested reader. | ||||
|   // -+ (k_46 * t_0^3 + k_47 * t_0^2 + k_48 * t_0 + k_49) * sqrt(k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59) = | ||||
|   //     k_70 * t_0^5 + k_71 * t_0^4 + k_72 * t_0^3 + k_73 * t_0^2 + k_74 * t_0 + k_61 | ||||
|   // (k_46 * t_0^3 + k_47 * t_0^2 + k_48 * t_0 + k_49)^2 * (k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59) = | ||||
|   //     (k_70 * t_0^5 + k_71 * t_0^4 + k_72 * t_0^3 + k_73 * t_0^2 + k_74 * t_0 + k_61)^2 | ||||
|   // 0 = | ||||
|   //     (k_46^2 * t_0^6 | ||||
|   //         + 2 * k_46 * k_47 * t_0^5 | ||||
|   //         + k_47^2 * t_0^4 + 2 * k_46 * k_48 * t_0^4 | ||||
|   //         + 2 * k_46 * k_49 * t_0^3 + 2 * k_47 * k_48 * t_0^3 | ||||
|   //         + k_48^2 * t_0^2 + 2 * k_47 * k_49 * t_0^2 | ||||
|   //         + 2 * k_48 * k_49 * t_0 | ||||
|   //         + k_49^2 | ||||
|   //     ) * (k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59) | ||||
|   //     - k_70^2 * t_0^10 | ||||
|   //     - 2 * k_70 * k_71 * t_0^9 | ||||
|   //     - (k_71^2 + 2 * k_70 * k_72) * t_0^8 | ||||
|   //     - 2 * (k_70 * k_73 + k_71 * k_72) * t_0^7 | ||||
|   //     - (k_72^2 + 2 * k_70 * k_74 + 2 * k_71 * k_73) * t_0^6 | ||||
|   //     - 2 * (k_70 * k_61 + k_71 * k_74 + k_72 * k_73) * t_0^5 | ||||
|   //     - (k_73^2 + 2 * k_71 * k_61 + 2 * k_72 * k_74) * t_0^4 | ||||
|   //     - 2 * (k_72 * k_61 + k_73 * k_74) * t_0^3 | ||||
|   //     - (k_74^2 + 2 * k_73 * k_61) * t_0^2 | ||||
|   //     - 2 * k_74 * k_61 * t_0 | ||||
|   //     - k_61^2 | ||||
|   // 0 = ak_10 * t_0^10 + ak_9 * t_0^9 + ak_8 * t_0^8 + ak_7 * t_0^7 + ak_6 * t_0^6 + ak_5 * t_0^5 + ak_4 * t_0^4 + ak_3 * t_0^3 + ak_2 * t_0^2 + ak_1 * t_0 + ak_0 | ||||
| 
 | ||||
|   //k[78] := k[46] * k[46]; | ||||
|   //k[79] := 2 * k[46] * k[47]; | ||||
|   //k[80] := k[47] * k[47] + 2 * k[46] * k[48]; | ||||
|   //k[81] := 2 * (k[46] * k[49] + k[47] * k[48]); | ||||
|   //k[82] := k[48] * k[48] + 2 * k[47] * k[49]; | ||||
|   //k[83] := 2 * k[48] * k[49]; | ||||
|   //k[84] := k[49] * k[49]; | ||||
|   //ak[0] := k[59] * k[84] - k[61] * k[61]; | ||||
|   //ak[1] := k[77] * k[84] + k[59] * k[83] - 2 * k[74] * k[61]; | ||||
|   //ak[2] := k[76] * k[84] + k[77] * k[83] + k[59] * k[82] - k[74] * k[74] - 2 * k[73] * k[61]; | ||||
|   //ak[3] := k[76] * k[83] + k[77] * k[82] + k[59] * k[81] + k[75] * k[84] | ||||
|   //  - 2 * (k[72] * k[61] + k[73] * k[74]); | ||||
|   //ak[4] := k[37] * k[84] + k[76] * k[82] + k[77] * k[81] + k[59] * k[80] + k[75] * k[83] - k[73] * k[73] | ||||
|   //  - 2 * (k[71] * k[61] + k[72] * k[74]); | ||||
|   //ak[5] := k[37] * k[83] + k[76] * k[81] + k[77] * k[80] + k[59] * k[79] + k[75] * k[82] | ||||
|   //  - 2 * (k[70] * k[61] + k[71] * k[74] + k[72] * k[73]); | ||||
|   //ak[6] := k[37] * k[82] + k[76] * k[80] + k[77] * k[79] + k[59] * k[78] + k[75] * k[81] - k[72] * k[72] | ||||
|   //  - 2 * (k[70] * k[74] + k[71] * k[73]); | ||||
|   //ak[7] := k[37] * k[81] + k[76] * k[79] + k[77] * k[78] + k[75] * k[80] - 2 * (k[70] * k[73] + k[71] * k[72]); | ||||
|   //ak[8] := k[37] * k[80] + k[75] * k[79] + k[76] * k[78] - k[71] * k[71] - 2 * k[70] * k[72]; | ||||
|   //ak[9] := k[37] * k[79] + k[75] * k[78] - 2 * k[70] * k[71]; | ||||
|   //ak[10] := k[37] * k[78] - k[70] * k[70]; | ||||
| end; | ||||
| 
 | ||||
| function TNeverTellMeTheOdds.CalcRockThrowCollisionOptions(constref AHailstone0, AHailstone1, AHailstone2: THailstone): | ||||
|   TInt64Array; | ||||
| var | ||||
|   a0, a1: TBigIntPolynomial; | ||||
|   a0Roots, a1Roots: TBigIntArray; | ||||
|   options: specialize TList<Int64>; | ||||
|   i, j: TBigInt; | ||||
|   val: Int64; | ||||
| begin | ||||
|   CalcCollisionPolynomials(AHailstone0, AHailstone1, AHailstone2, a0, a1); | ||||
|   a0Roots := TPolynomialRoots.BisectInteger(a0, 64); | ||||
|   a1Roots := TPolynomialRoots.BisectInteger(a1, 64); | ||||
| 
 | ||||
|   options := specialize TList<Int64>.Create; | ||||
|   for i in a0Roots do | ||||
|     for j in a1Roots do | ||||
|       if (i = j) and i.TryToInt64(val) then | ||||
|         options.Add(val); | ||||
|   Result := options.ToArray; | ||||
|   options.Free; | ||||
| end; | ||||
| 
 | ||||
| function TNeverTellMeTheOdds.ValidateRockThrow(constref AHailstone0, AHailstone1, AHailstone2: THailstone; const AT0, | ||||
|   AT1: Int64): Int64; | ||||
| var | ||||
|   divisor, t: Int64; | ||||
|   rock: THailstone; | ||||
| begin | ||||
|   // V_x = (V_0 * t_0 - V_1 * t_1 + P_0 - P_1) / (t_0 - t_1) | ||||
|   divisor := AT0 - AT1; | ||||
|   rock := THailstone.Create; | ||||
|   rock.V0 := (AHailstone0.V0 * AT0 - AHailstone1.V0 * AT1 + AHailstone0.P0 - AHailstone1.P0) div divisor; | ||||
|   rock.V1 := (AHailstone0.V1 * AT0 - AHailstone1.V1 * AT1 + AHailstone0.P1 - AHailstone1.P1) div divisor; | ||||
|   rock.V2 := (AHailstone0.V2 * AT0 - AHailstone1.V2 * AT1 + AHailstone0.P2 - AHailstone1.P2) div divisor; | ||||
| 
 | ||||
|   // P_x = (V_0 - V_x) * t_0 + P_0 | ||||
|   rock.P0 := (AHailstone0.V0 - rock.V0) * AT0 + AHailstone0.P0; | ||||
|   rock.P1 := (AHailstone0.V1 - rock.V1) * AT0 + AHailstone0.P1; | ||||
|   rock.P2 := (AHailstone0.V2 - rock.V2) * AT0 + AHailstone0.P2; | ||||
| 
 | ||||
|   Result := rock.P0 + rock.P1 + rock.P2; | ||||
| 
 | ||||
|   // Checks collision with the third hailstone. | ||||
|   if ((AHailstone2.V0 = rock.V0) and (AHailstone2.P0 <> rock.P0)) | ||||
|   or ((AHailstone2.V1 = rock.V1) and (AHailstone2.P1 <> rock.P1)) | ||||
|   or ((AHailstone2.V2 = rock.V2) and (AHailstone2.P2 <> rock.P2)) then | ||||
|     Result := 0 | ||||
|   else begin | ||||
|     t := (AHailstone2.P0 - rock.P0) div (rock.V0 - AHailstone2.V0); | ||||
|     if (t <> (AHailstone2.P1 - rock.P1) div (rock.V1 - AHailstone2.V1)) | ||||
|     or (t <> (AHailstone2.P2 - rock.P2) div (rock.V2 - AHailstone2.V2)) then | ||||
|       Result := 0; | ||||
|   end; | ||||
| 
 | ||||
|   rock.Free; | ||||
| end; | ||||
| 
 | ||||
| constructor TNeverTellMeTheOdds.Create(const AMin: Int64; const AMax: Int64); | ||||
| begin | ||||
|   FMin := AMin; | ||||
|   FMax := AMax; | ||||
|   FHailStones := THailstones.Create; | ||||
|   FHailstones := THailstones.Create; | ||||
| end; | ||||
| 
 | ||||
| destructor TNeverTellMeTheOdds.Destroy; | ||||
| begin | ||||
|   FHailStones.Free; | ||||
|   FHailstones.Free; | ||||
|   inherited Destroy; | ||||
| end; | ||||
| 
 | ||||
| procedure TNeverTellMeTheOdds.ProcessDataLine(const ALine: string); | ||||
| var | ||||
|   split: TStringArray; | ||||
|   hailstone: THailstone; | ||||
| begin | ||||
|   split := ALine.Split([',', '@']); | ||||
|   hailstone.X := StrToInt64(Trim(split[0])); | ||||
|   hailstone.Y := StrToInt64(Trim(split[1])); | ||||
|   hailstone.Z := StrToInt64(Trim(split[2])); | ||||
|   hailstone.VX := StrToInt(Trim(split[3])); | ||||
|   hailstone.VY := StrToInt(Trim(split[4])); | ||||
|   hailstone.VZ := StrToInt(Trim(split[5])); | ||||
|   FHailStones.Add(hailstone); | ||||
|   FHailstones.Add(THailstone.Create(ALine)); | ||||
| end; | ||||
| 
 | ||||
| procedure TNeverTellMeTheOdds.Finish; | ||||
| var | ||||
|   i, j: Integer; | ||||
| begin | ||||
|   for i := 0 to FHailStones.Count - 2 do | ||||
|     for j := i + 1 to FHailStones.Count - 1 do | ||||
|       if AreIntersecting(FHailStones[i], FHailStones[j]) then | ||||
|   for i := 0 to FHailstones.Count - 2 do | ||||
|     for j := i + 1 to FHailstones.Count - 1 do | ||||
|       if AreIntersecting(FHailstones[i], FHailstones[j]) then | ||||
|         Inc(FPart1); | ||||
| 
 | ||||
|   if FHailstones.Count >= 3 then | ||||
|     FPart2 := FindRockThrow(0, 1, 2); | ||||
| end; | ||||
| 
 | ||||
| function TNeverTellMeTheOdds.GetDataFileName: string; | ||||
|  | ||||
| @ -40,10 +40,6 @@ | ||||
|         <Filename Value="UGearRatiosTestCases.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|       <Unit> | ||||
|         <Filename Value="..\USolver.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|       <Unit> | ||||
|         <Filename Value="UBaseTestCases.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
| @ -140,6 +136,18 @@ | ||||
|         <Filename Value="UNeverTellMeTheOddsTestCases.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|       <Unit> | ||||
|         <Filename Value="UPolynomialTestCases.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|       <Unit> | ||||
|         <Filename Value="UPolynomialRootsTestCases.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|       <Unit> | ||||
|         <Filename Value="UBigIntTestCases.pas"/> | ||||
|         <IsPartOfProject Value="True"/> | ||||
|       </Unit> | ||||
|     </Units> | ||||
|   </ProjectOptions> | ||||
|   <CompilerOptions> | ||||
|  | ||||
| @ -9,7 +9,7 @@ uses | ||||
|   UHotSpringsTestCases, UPointOfIncidenceTestCases, UParabolicReflectorDishTestCases, ULensLibraryTestCases, | ||||
|   UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases, | ||||
|   UPulsePropagationTestCases, UStepCounterTestCases, USandSlabsTestCases, ULongWalkTestCases, | ||||
|   UNeverTellMeTheOddsTestCases; | ||||
|   UNeverTellMeTheOddsTestCases, UBigIntTestCases, UPolynomialTestCases, UPolynomialRootsTestCases; | ||||
| 
 | ||||
| {$R *.res} | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										1033
									
								
								tests/UBigIntTestCases.pas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1033
									
								
								tests/UBigIntTestCases.pas
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -33,6 +33,7 @@ type | ||||
|     function CreateSolver: ISolver; override; | ||||
|   published | ||||
|     procedure TestPart1; | ||||
|     procedure TestPart2; | ||||
|   end; | ||||
| 
 | ||||
|   { TNeverTellMeTheOddsExampleTestCase } | ||||
| @ -42,6 +43,7 @@ type | ||||
|     function CreateSolver: ISolver; override; | ||||
|   published | ||||
|     procedure TestPart1; | ||||
|     procedure TestPart2; | ||||
|   end; | ||||
| 
 | ||||
|   { TNeverTellMeTheOddsTestCase } | ||||
| @ -77,6 +79,11 @@ begin | ||||
|   AssertEquals(15107, FSolver.GetResultPart1); | ||||
| end; | ||||
| 
 | ||||
| procedure TNeverTellMeTheOddsFullDataTestCase.TestPart2; | ||||
| begin | ||||
|   AssertEquals(856642398547748, FSolver.GetResultPart2); | ||||
| end; | ||||
| 
 | ||||
| { TNeverTellMeTheOddsExampleTestCase } | ||||
| 
 | ||||
| function TNeverTellMeTheOddsExampleTestCase.CreateSolver: ISolver; | ||||
| @ -89,6 +96,11 @@ begin | ||||
|   AssertEquals(2, FSolver.GetResultPart1); | ||||
| end; | ||||
| 
 | ||||
| procedure TNeverTellMeTheOddsExampleTestCase.TestPart2; | ||||
| begin | ||||
|   AssertEquals(47, FSolver.GetResultPart2); | ||||
| end; | ||||
| 
 | ||||
| { TNeverTellMeTheOddsTestCase } | ||||
| 
 | ||||
| function TNeverTellMeTheOddsTestCase.CreateSolver: ISolver; | ||||
|  | ||||
							
								
								
									
										138
									
								
								tests/UPolynomialRootsTestCases.pas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								tests/UPolynomialRootsTestCases.pas
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,138 @@ | ||||
| { | ||||
|   Solutions to the Advent Of Code. | ||||
|   Copyright (C) 2024  Stefan Müller | ||||
| 
 | ||||
|   This program is free software: you can redistribute it and/or modify it under | ||||
|   the terms of the GNU General Public License as published by the Free Software | ||||
|   Foundation, either version 3 of the License, or (at your option) any later | ||||
|   version. | ||||
| 
 | ||||
|   This program is distributed in the hope that it will be useful, but WITHOUT | ||||
|   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||
|   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. | ||||
| 
 | ||||
|   You should have received a copy of the GNU General Public License along with | ||||
|   this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| } | ||||
| 
 | ||||
| unit UPolynomialRootsTestCases; | ||||
| 
 | ||||
| {$mode ObjFPC}{$H+} | ||||
| 
 | ||||
| interface | ||||
| 
 | ||||
| uses | ||||
|   Classes, SysUtils, fpcunit, testregistry, UPolynomial, UPolynomialRoots, UBigInt; | ||||
| 
 | ||||
| type | ||||
| 
 | ||||
|   { TPolynomialRootsTestCase } | ||||
| 
 | ||||
|   TPolynomialRootsTestCase = class(TTestCase) | ||||
|   private | ||||
|     procedure AssertBisectIntervals(AIsolatingIntervals: TIsolatingIntervalArray; constref AExpectedRoots: | ||||
|       array of Cardinal); | ||||
|     procedure AssertBisectIntegers(ARoots: TBigIntArray; constref AExpectedRoots: array of Cardinal); | ||||
|   published | ||||
|     procedure TestBisectNoBound; | ||||
|     procedure TestBisectWithBound; | ||||
|     procedure TestBisectInteger; | ||||
|   end; | ||||
| 
 | ||||
| implementation | ||||
| 
 | ||||
| { TPolynomialRootsTestCase } | ||||
| 
 | ||||
| procedure TPolynomialRootsTestCase.AssertBisectIntervals(AIsolatingIntervals: TIsolatingIntervalArray; | ||||
|   constref AExpectedRoots: array of Cardinal); | ||||
| var | ||||
|   exp: Cardinal; | ||||
|   found: Boolean; | ||||
|   i, foundIndex: Integer; | ||||
| begin | ||||
|   AssertEquals('Unexpected number of isolating intervals.', Length(AExpectedRoots), Length(AIsolatingIntervals)); | ||||
|   for exp in AExpectedRoots do | ||||
|   begin | ||||
|     found := False; | ||||
|     for i := 0 to Length(AIsolatingIntervals) - 1 do | ||||
|       if (AIsolatingIntervals[i].A <= exp) and (exp <= AIsolatingIntervals[i].B) then | ||||
|       begin | ||||
|         found := True; | ||||
|         foundIndex := i; | ||||
|         Break; | ||||
|       end; | ||||
|     AssertTrue('No isolating interval for expected root ' + IntToStr(exp) + ' found.', found); | ||||
|     Delete(AIsolatingIntervals, foundIndex, 1); | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| procedure TPolynomialRootsTestCase.AssertBisectIntegers(ARoots: TBigIntArray; constref AExpectedRoots: | ||||
|   array of Cardinal); | ||||
| var | ||||
|   exp: Cardinal; | ||||
|   found: Boolean; | ||||
|   i, foundIndex: Integer; | ||||
| begin | ||||
|   AssertEquals('Unexpected number of integer roots.', Length(AExpectedRoots), Length(ARoots)); | ||||
|   for exp in AExpectedRoots do | ||||
|   begin | ||||
|     found := False; | ||||
|     for i := 0 to Length(ARoots) - 1 do | ||||
|       if ARoots[i] = exp then | ||||
|       begin | ||||
|         found := True; | ||||
|         foundIndex := i; | ||||
|         Break; | ||||
|       end; | ||||
|     AssertTrue('Expected root ' + IntToStr(exp) + ' not found.', found); | ||||
|     Delete(ARoots, foundIndex, 1); | ||||
|   end; | ||||
| end; | ||||
| 
 | ||||
| procedure TPolynomialRootsTestCase.TestBisectNoBound; | ||||
| const | ||||
|   expRoots: array of Cardinal = (34000, 23017, 5); | ||||
| var | ||||
|   a: TBigIntPolynomial; | ||||
|   r: TIsolatingIntervalArray; | ||||
| begin | ||||
|   // y = 3 * (x - 34000) * (x - 23017) * (x - 5) * (x^2 - 19) * (x + 112) | ||||
|   //   = 3 * x^6 - 170730 * x^5 + 2329429920 * x^4 + 251300082690 * x^3 - 1270471872603 * x^2 + 4774763204640 * x - 24979889760000 | ||||
|   a := TBigIntPolynomial.Create([-24979889760000, 4774763204640, -1270471872603, 251300082690, 2329429920, -170730, 3]); | ||||
|   r := TPolynomialRoots.BisectIsolation(a); | ||||
|   AssertBisectIntervals(r, expRoots); | ||||
| end; | ||||
| 
 | ||||
| procedure TPolynomialRootsTestCase.TestBisectWithBound; | ||||
| const | ||||
|   expRoots: array of Cardinal = (23017, 5); | ||||
| var | ||||
|   a: TBigIntPolynomial; | ||||
|   r: TIsolatingIntervalArray; | ||||
| begin | ||||
|   // y = 3 * (x - 34000) * (x - 23017) * (x - 5) * (x^2 - 19) * (x + 112) | ||||
|   //   = 3 * x^6 - 170730 * x^5 + 2329429920 * x^4 + 251300082690 * x^3 - 1270471872603 * x^2 + 4774763204640 * x - 24979889760000 | ||||
|   a := TBigIntPolynomial.Create([-24979889760000, 4774763204640, -1270471872603, 251300082690, 2329429920, -170730, 3]); | ||||
|   r := TPolynomialRoots.BisectIsolation(a, 15); | ||||
|   AssertBisectIntervals(r, expRoots); | ||||
| end; | ||||
| 
 | ||||
| procedure TPolynomialRootsTestCase.TestBisectInteger; | ||||
| const | ||||
|   expRoots: array of Cardinal = (23017, 5); | ||||
| var | ||||
|   a: TBigIntPolynomial; | ||||
|   r: TBigIntArray; | ||||
| begin | ||||
|   // y = 3 * (x - 34000) * (x - 23017) * (x - 5) * (x^2 - 19) * (x + 112) | ||||
|   //   = 3 * x^6 - 170730 * x^5 + 2329429920 * x^4 + 251300082690 * x^3 - 1270471872603 * x^2 + 4774763204640 * x - 24979889760000 | ||||
|   a := TBigIntPolynomial.Create([-24979889760000, 4774763204640, -1270471872603, 251300082690, 2329429920, -170730, 3]); | ||||
|   r := TPolynomialRoots.BisectInteger(a, 15); | ||||
|   AssertBisectIntegers(r, expRoots); | ||||
| end; | ||||
| 
 | ||||
| initialization | ||||
| 
 | ||||
|   RegisterTest(TPolynomialRootsTestCase); | ||||
| end. | ||||
| 
 | ||||
							
								
								
									
										187
									
								
								tests/UPolynomialTestCases.pas
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								tests/UPolynomialTestCases.pas
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | ||||
| { | ||||
|   Solutions to the Advent Of Code. | ||||
|   Copyright (C) 2024  Stefan Müller | ||||
| 
 | ||||
|   This program is free software: you can redistribute it and/or modify it under | ||||
|   the terms of the GNU General Public License as published by the Free Software | ||||
|   Foundation, either version 3 of the License, or (at your option) any later | ||||
|   version. | ||||
| 
 | ||||
|   This program is distributed in the hope that it will be useful, but WITHOUT | ||||
|   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||
|   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. | ||||
| 
 | ||||
|   You should have received a copy of the GNU General Public License along with | ||||
|   this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| } | ||||
| 
 | ||||
| unit UPolynomialTestCases; | ||||
| 
 | ||||
| {$mode ObjFPC}{$H+} | ||||
| 
 | ||||
| interface | ||||
| 
 | ||||
| uses | ||||
|   Classes, SysUtils, fpcunit, testregistry, UPolynomial, UBigInt; | ||||
| 
 | ||||
| type | ||||
| 
 | ||||
|   { TBigIntPolynomialTestCase } | ||||
| 
 | ||||
|   TBigIntPolynomialTestCase = class(TTestCase) | ||||
|   private | ||||
|     procedure TestCreateWithDegree(const ACoefficients: array of TBigInt; const ADegree: Integer); | ||||
|   published | ||||
|     procedure TestCreate; | ||||
|     procedure TestCreateDegreeZero; | ||||
|     procedure TestEqual; | ||||
|     procedure TestUnequalSameLength; | ||||
|     procedure TestUnequalDifferentLength; | ||||
|     procedure TestTrimLeadingZeros; | ||||
|     procedure TestCalcValueAt; | ||||
|     procedure TestSignVariations; | ||||
|     procedure TestScaleByPowerOfTwo; | ||||
|     procedure TestScaleVariable; | ||||
|     procedure TestScaleVariableByPowerOfTwo; | ||||
|     procedure TestTranslateVariableByOne; | ||||
|     procedure TestRevertOrderOfCoefficients; | ||||
|     procedure TestDivideByVariable; | ||||
|   end; | ||||
| 
 | ||||
| implementation | ||||
| 
 | ||||
| { TBigIntPolynomialTestCase } | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestCreateWithDegree(const ACoefficients: array of TBigInt; const ADegree: Integer); | ||||
| var | ||||
|   a: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create(ACoefficients); | ||||
|   AssertEquals('Degree of created polynomial incorrect.', ADegree, a.Degree); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestCreate; | ||||
| begin | ||||
|   TestCreateWithDegree([992123, 7, 20, 4550022], 3); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestCreateDegreeZero; | ||||
| begin | ||||
|   TestCreateWithDegree([4007], 0); | ||||
|   TestCreateWithDegree([], 0); | ||||
|   TestCreateWithDegree([0], 0); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestEqual; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([10, 7, 5, 1034]); | ||||
|   b := TBigIntPolynomial.Create([10, 7, 5, 1034]); | ||||
|   AssertTrue('Polynomials are not equal.', a = b); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestUnequalSameLength; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([103, 7, 5, 10]); | ||||
|   b := TBigIntPolynomial.Create([1034, 7, 5, 10]); | ||||
|   AssertTrue('Polynomials are equal.', a <> b); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestUnequalDifferentLength; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([40000, 10, 7, 5, 1034]); | ||||
|   b := TBigIntPolynomial.Create([10, 7, 5, 1034]); | ||||
|   AssertTrue('Polynomials are equal.', a <> b); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestTrimLeadingZeros; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([10, 7, 5, 1034, 0, 0]); | ||||
|   b := TBigIntPolynomial.Create([10, 7, 5, 1034]); | ||||
|   AssertTrue('Polynomials are not equal.', a = b); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestCalcValueAt; | ||||
| var | ||||
|   a: TBigIntPolynomial; | ||||
|   exp: TBigInt; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([80, 892477222, 0, 921556, 7303]); | ||||
|   exp:= TBigInt.FromInt64(16867124285); | ||||
|   AssertTrue('Polynomial evaluation unexpected.', a.CalcValueAt(15) = exp); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestSignVariations; | ||||
| var | ||||
|   a: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([-10, 15, 0, 10, -20, -15, 0, 0, 5, 10, -10]); | ||||
|   AssertEquals(4, a.CalcSignVariations); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestScaleByPowerOfTwo; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([10, 7, 5, 1034]).ScaleByPowerOfTwo(7); | ||||
|   b := TBigIntPolynomial.Create([128 * 10, 128 * 7, 128 * 5, 128 * 1034]); | ||||
|   AssertTrue('Polynomials are not equal.', a = b); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestScaleVariable; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([10, 7, 5, 1034]).ScaleVariable(TBigInt.FromInt64(10)); | ||||
|   b := TBigIntPolynomial.Create([10, 70, 500, 1034000]); | ||||
|   AssertTrue('Polynomials are not equal.', a = b); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestScaleVariableByPowerOfTwo; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([10, 7, 5, 1034]).ScaleVariableByPowerOfTwo(5); | ||||
|   b := TBigIntPolynomial.Create([10, 7 * 32, 5 * 32 * 32, 1034 * 32 * 32 * 32]); | ||||
|   AssertTrue('Polynomials are not equal.', a = b); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestTranslateVariableByOne; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([10, 7, 5, 1034]).TranslateVariableByOne; | ||||
|   b := TBigIntPolynomial.Create([1056, 3119, 3107, 1034]); | ||||
|   AssertTrue('Polynomials are not equal.', a = b); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestRevertOrderOfCoefficients; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([0, 10, 7, 5, 1034]).RevertOrderOfCoefficients; | ||||
|   b := TBigIntPolynomial.Create([1034, 5, 7, 10]); | ||||
|   AssertTrue('Polynomials are not equal.', a = b); | ||||
| end; | ||||
| 
 | ||||
| procedure TBigIntPolynomialTestCase.TestDivideByVariable; | ||||
| var | ||||
|   a, b: TBigIntPolynomial; | ||||
| begin | ||||
|   a := TBigIntPolynomial.Create([0, 10, 7, 5, 1034]).DivideByVariable; | ||||
|   b := TBigIntPolynomial.Create([10, 7, 5, 1034]); | ||||
|   AssertTrue('Polynomials are not equal.', a = b); | ||||
| end; | ||||
| 
 | ||||
| initialization | ||||
| 
 | ||||
|   RegisterTest(TBigIntPolynomialTestCase); | ||||
| end. | ||||
| 
 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user