У меня есть общий массив TPair
записей, которые содержат целочисленный индекс и двойное значение. Я сортирую этот массив по значению каждого TPair
, создав TComparer<TPair<Integer, Double>>
, который просто вызывает значение по умолчанию TComparer<Double>
для каждого значения TPair
. Тем не менее, я получаю нарушение прав доступа при вызове сортировки. Кажется, проблема связана с тем, что некоторые из моих значений NaN
. Я не контролирую тот факт, что некоторые значения будут NaN
, поэтому мне нужно обойти это.
Я пытался проверить NaN
перед вызовом значения по умолчанию TComparer
для двойных чисел и вместо замены NaN
на Low(Integer)
, но это не помогло. На данный момент я сделал тестовое приложение, которое воспроизводит проблему, и я вижу, что функция TArray.QuickSort
выполняет тысячи сравнений для массива из 3 элементов ... не знаю, что происходит.
Распечатка двух пар, сравниваемых в каждом вызове с любым из пользовательских компараторов, дает такой результат:
[0] Left = (0, 1.00); Right = (1, NAN)
[1] Left = (1, NAN); Right = (1, NAN)
[2] Left = (2, 3.00); Right = (1, NAN)
[3] Left = (0, 0.00); Right = (1, NAN)
[4] Left = (0, 0.00); Right = (1, NAN)
...
[7328] Left = (0, 0.00); Right = (1, NAN)
[7329] Left = (58976, 0.00); Right = (1, NAN)
Нарушение прав доступа происходит при последующем вызове моего компаратора после записи этой последней строки.
Вот полное тестовое приложение:
program Project51;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, Generics.Collections, Generics.Defaults, Math;
type
TIntDblPair = TPair<Integer, Double>;
var
Comparer1, Comparer2 : IComparer<TIntDblPair>;
ResultSet : TArray<TIntDblPair>;
nCompares : Integer;
procedure Test(AComparer : IComparer<TIntDblPair>);
var
I : Integer;
begin
TArray.Sort<TIntDblPair>(ResultSet, AComparer);
for I := 0 to Length(ResultSet) - 1 do
WriteLn(Format('%d: %f', [ResultSet[I].Key, ResultSet[I].Value]));
WriteLn;
end;
begin
try
SetLength(ResultSet, 3);
ResultSet[0] := TIntDblPair.Create(0, 1);
ResultSet[1] := TIntDblPair.Create(1, NaN);
ResultSet[2] := TIntDblPair.Create(2, 3);
nCompares := 0;
Comparer1 := TComparer<TIntDblPair>.Construct(
function(const Left, Right: TIntDblPair): Integer
begin
WriteLn(Format('[%d] Left = (%d, %f); Right = (%d, %f)', [nCompares, Left.Key, Left.Value, Right.Key, Right.Value]));
Result := TComparer<Double>.Default.Compare(Right.Value, Left.Value);
Inc(nCompares);
end
);
// Test(Comparer1);
nCompares := 0;
Comparer2 := TComparer<TIntDblPair>.Construct(
function(const Left, Right: TIntDblPair): Integer
begin
WriteLn(Format('[%d] Left = (%d, %f); Right = (%d, %f)', [nCompares, Left.Key, Left.Value, Right.Key, Right.Value]));
if IsNaN(Right.Value) then
Result := TComparer<Double>.Default.Compare(Low(Integer), Left.Value)
else if IsNaN(Left.Value) then
Result := TComparer<Double>.Default.Compare(Right.Value, Low(Integer))
else
Result := TComparer<Double>.Default.Compare(Right.Value, Left.Value);
Inc(nCompares);
end
);
Test(Comparer2);
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Реми Лебо указал, что мои проверки в Comparer2 не были завершены. Замена Comparer2 на следующее заставляет этот вызов сортировки возвращать ожидаемый результат. Таким образом, кажется очевидным, что проблема в том, что двойной компаратор по умолчанию пытается сравнить NaN. Это ошибка в реализации компаратора?
Comparer2 := TComparer<TIntDblPair>.Construct(
function(const Left, Right: TIntDblPair): Integer
begin
if IsNaN(Right.Value) and IsNaN(Left.Value) then
Result := 0
else if IsNaN(Right.Value) then
Result := TComparer<Double>.Default.Compare(Low(Integer), Left.Value)
else if IsNaN(Left.Value) then
Result := TComparer<Double>.Default.Compare(Right.Value, Low(Integer))
else
Result := TComparer<Double>.Default.Compare(Right.Value, Left.Value);
end
);