Merge branch 'day24-analytical'

This commit is contained in:
Stefan Müller 2024-05-27 02:53:03 +02:00
commit 859a5db921
12 changed files with 3313 additions and 35 deletions

View File

@ -137,6 +137,18 @@
<Filename Value="solvers\UNeverTellMeTheOdds.pas"/> <Filename Value="solvers\UNeverTellMeTheOdds.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit> </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> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

763
UBigInt.pas Normal file
View 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.

View File

@ -22,7 +22,7 @@ unit UNumberTheory;
interface interface
uses uses
Classes, SysUtils; Classes, SysUtils, Generics.Collections, Math;
type type
@ -34,6 +34,52 @@ type
class function LeastCommonMultiple(AValue1, AValue2: Int64): Int64; class function LeastCommonMultiple(AValue1, AValue2: Int64): Int64;
end; 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 implementation
{ TNumberTheory } { TNumberTheory }
@ -58,5 +104,177 @@ begin
Result := (Abs(AValue1) div GreatestCommonDivisor(AValue1, AValue2)) * Abs(AValue2); Result := (Abs(AValue1) div GreatestCommonDivisor(AValue1, AValue2)) * Abs(AValue2);
end; 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. end.

297
UPolynomial.pas Normal file
View 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
View 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.

View File

@ -1,6 +1,6 @@
{ {
Solutions to the Advent Of Code. 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 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 the terms of the GNU General Public License as published by the Free Software
@ -22,26 +22,37 @@ unit UNeverTellMeTheOdds;
interface interface
uses uses
Classes, SysUtils, Generics.Collections, Math, USolver; Classes, SysUtils, Generics.Collections, Math, USolver, UNumberTheory, UBigInt, UPolynomial, UPolynomialRoots;
type type
{ THailstone } { THailstone }
THailstone = record THailstone = class
X, Y, Z: Int64; public
VX, VY, VZ: Integer; P0, P1, P2: Int64;
V0, V1, V2: Integer;
constructor Create(const ALine: string);
constructor Create;
end; end;
THailstones = specialize TList<THailstone>; THailstones = specialize TObjectList<THailstone>;
TInt64Array = array of Int64;
{ TNeverTellMeTheOdds } { TNeverTellMeTheOdds }
TNeverTellMeTheOdds = class(TSolver) TNeverTellMeTheOdds = class(TSolver)
private private
FMin, FMax: Int64; FMin, FMax: Int64;
FHailStones: THailstones; FHailstones: THailstones;
function AreIntersecting(constref AHailstone1, AHailstone2: THailstone): Boolean; 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 public
constructor Create(const AMin: Int64 = 200000000000000; const AMax: Int64 = 400000000000000); constructor Create(const AMin: Int64 = 200000000000000; const AMax: Int64 = 400000000000000);
destructor Destroy; override; destructor Destroy; override;
@ -53,6 +64,26 @@ type
implementation 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 } { TNeverTellMeTheOdds }
function TNeverTellMeTheOdds.AreIntersecting(constref AHailstone1, AHailstone2: THailstone): Boolean; function TNeverTellMeTheOdds.AreIntersecting(constref AHailstone1, AHailstone2: THailstone): Boolean;
@ -60,58 +91,432 @@ var
m1, m2, x, y: Double; m1, m2, x, y: Double;
begin begin
Result := False; Result := False;
m1 := AHailstone1.VY / AHailstone1.VX; m1 := AHailstone1.V1 / AHailstone1.V0;
m2 := AHailstone2.VY / AHailstone2.VX; m2 := AHailstone2.V1 / AHailstone2.V0;
if m1 <> m2 then if m1 <> m2 then
begin 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) if (FMin <= x) and (x <= FMax)
and (x * Sign(AHailstone1.VX) >= AHailstone1.X * Sign(AHailstone1.VX)) and (x * Sign(AHailstone1.V0) >= AHailstone1.P0 * Sign(AHailstone1.V0))
and (x * Sign(AHailstone2.VX) >= AHailstone2.X * Sign(AHailstone2.VX)) then and (x * Sign(AHailstone2.V0) >= AHailstone2.P0 * Sign(AHailstone2.V0))
then
begin begin
y := m1 * (x - AHailstone1.X) + AHailstone1.Y; y := m1 * (x - AHailstone1.P0) + AHailstone1.P1;
if (FMin <= y) and (y <= FMax) then if (FMin <= y) and (y <= FMax) then
Result := True Result := True
end; end;
end; 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); constructor TNeverTellMeTheOdds.Create(const AMin: Int64; const AMax: Int64);
begin begin
FMin := AMin; FMin := AMin;
FMax := AMax; FMax := AMax;
FHailStones := THailstones.Create; FHailstones := THailstones.Create;
end; end;
destructor TNeverTellMeTheOdds.Destroy; destructor TNeverTellMeTheOdds.Destroy;
begin begin
FHailStones.Free; FHailstones.Free;
inherited Destroy; inherited Destroy;
end; end;
procedure TNeverTellMeTheOdds.ProcessDataLine(const ALine: string); procedure TNeverTellMeTheOdds.ProcessDataLine(const ALine: string);
var
split: TStringArray;
hailstone: THailstone;
begin begin
split := ALine.Split([',', '@']); FHailstones.Add(THailstone.Create(ALine));
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);
end; end;
procedure TNeverTellMeTheOdds.Finish; procedure TNeverTellMeTheOdds.Finish;
var var
i, j: Integer; i, j: Integer;
begin begin
for i := 0 to FHailStones.Count - 2 do for i := 0 to FHailstones.Count - 2 do
for j := i + 1 to FHailStones.Count - 1 do for j := i + 1 to FHailstones.Count - 1 do
if AreIntersecting(FHailStones[i], FHailStones[j]) then if AreIntersecting(FHailstones[i], FHailstones[j]) then
Inc(FPart1); Inc(FPart1);
if FHailstones.Count >= 3 then
FPart2 := FindRockThrow(0, 1, 2);
end; end;
function TNeverTellMeTheOdds.GetDataFileName: string; function TNeverTellMeTheOdds.GetDataFileName: string;

View File

@ -40,10 +40,6 @@
<Filename Value="UGearRatiosTestCases.pas"/> <Filename Value="UGearRatiosTestCases.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit> </Unit>
<Unit>
<Filename Value="..\USolver.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit> <Unit>
<Filename Value="UBaseTestCases.pas"/> <Filename Value="UBaseTestCases.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
@ -140,6 +136,18 @@
<Filename Value="UNeverTellMeTheOddsTestCases.pas"/> <Filename Value="UNeverTellMeTheOddsTestCases.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit> </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> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

View File

@ -9,7 +9,7 @@ uses
UHotSpringsTestCases, UPointOfIncidenceTestCases, UParabolicReflectorDishTestCases, ULensLibraryTestCases, UHotSpringsTestCases, UPointOfIncidenceTestCases, UParabolicReflectorDishTestCases, ULensLibraryTestCases,
UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases, UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases,
UPulsePropagationTestCases, UStepCounterTestCases, USandSlabsTestCases, ULongWalkTestCases, UPulsePropagationTestCases, UStepCounterTestCases, USandSlabsTestCases, ULongWalkTestCases,
UNeverTellMeTheOddsTestCases; UNeverTellMeTheOddsTestCases, UBigIntTestCases, UPolynomialTestCases, UPolynomialRootsTestCases;
{$R *.res} {$R *.res}

1033
tests/UBigIntTestCases.pas Normal file

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ type
function CreateSolver: ISolver; override; function CreateSolver: ISolver; override;
published published
procedure TestPart1; procedure TestPart1;
procedure TestPart2;
end; end;
{ TNeverTellMeTheOddsExampleTestCase } { TNeverTellMeTheOddsExampleTestCase }
@ -42,6 +43,7 @@ type
function CreateSolver: ISolver; override; function CreateSolver: ISolver; override;
published published
procedure TestPart1; procedure TestPart1;
procedure TestPart2;
end; end;
{ TNeverTellMeTheOddsTestCase } { TNeverTellMeTheOddsTestCase }
@ -77,6 +79,11 @@ begin
AssertEquals(15107, FSolver.GetResultPart1); AssertEquals(15107, FSolver.GetResultPart1);
end; end;
procedure TNeverTellMeTheOddsFullDataTestCase.TestPart2;
begin
AssertEquals(856642398547748, FSolver.GetResultPart2);
end;
{ TNeverTellMeTheOddsExampleTestCase } { TNeverTellMeTheOddsExampleTestCase }
function TNeverTellMeTheOddsExampleTestCase.CreateSolver: ISolver; function TNeverTellMeTheOddsExampleTestCase.CreateSolver: ISolver;
@ -89,6 +96,11 @@ begin
AssertEquals(2, FSolver.GetResultPart1); AssertEquals(2, FSolver.GetResultPart1);
end; end;
procedure TNeverTellMeTheOddsExampleTestCase.TestPart2;
begin
AssertEquals(47, FSolver.GetResultPart2);
end;
{ TNeverTellMeTheOddsTestCase } { TNeverTellMeTheOddsTestCase }
function TNeverTellMeTheOddsTestCase.CreateSolver: ISolver; function TNeverTellMeTheOddsTestCase.CreateSolver: ISolver;

View 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.

View 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.