Сортировка массива парных чисел с NaN в нем

Это больше похоже на вопрос "Можете ли вы объяснить этот", чем что-либо другое.

Я столкнулся с проблемой на работе, где мы использовали значения NaN в таблице, но когда таблица была отсортирована, это получилось очень странным, странным образом. Я подумал, что NaN что-то мешает, поэтому я написал тестовое приложение, чтобы узнать, правда ли это. Это то, что я сделал.

static void Main(string[] args)
{
    double[] someArray = { 4.0, 2.0, double.NaN, 1.0, 5.0, 3.0, double.NaN, 10.0, 9.0, 8.0 };

    foreach (double db in someArray)
    {
        Console.WriteLine(db);
    }

    Array.Sort(someArray);
    Console.WriteLine("\n\n");
    foreach (double db in someArray)
    {
        Console.WriteLine(db);
    }

    Console.ReadLine();
}

Что дало результат:

До:

4,2,NaN,1,5,3,NaN,10,9,8

После:

1,4,NaN,2,3,5,8,9,10,NaN

Итак, да, NaN каким-то образом сделал отсортированный массив отсортированным странным образом.

Процитировать Фрай; "Почему это?"

Ответ 1

Изменить (окончание. окончание.): Это ошибка.

См. отчет об ошибке Ошибка в списке < double/single > .Sort() [.NET35] в списке, который содержит double.NaN и пойдите, дайте Хансу Пассану голосование на Почему .NET 4.0 сортирует этот массив по-другому, чем .NET 3.5?, из которого я разорвал ссылку.

Исторические размышления

[См. сообщение: Почему .NET 4.0 сортирует этот массив по-другому, чем .NET 3.5?, где, надеюсь, более полезное обсуждение этой конкретной проблемы может быть выдумал для реального. Я также разместил здесь этот ответ.]

Поведение, указанное в .NET4 от Phil, указано в CompareTo. См. double.CompareTo для .NET4. Это то же поведение, что и в .NET35, но должно быть согласованным в обеих версиях, по документации метода...

Array.Sort(double[]): кажется, не использует CompareTo(double[]), как ожидалось, и это может быть очень ошибкой - обратите внимание на разницу в Array.Sort(object []) и Array.Sort(double []) ниже. Мне хотелось бы уточнить/исправить следующее:

В любом случае ответы с использованием > и < и == объясняют, почему эти операторы не работают, но не могут объяснить, почему Array.Sort приводит к неожиданному выводу. Вот некоторые из моих находок, так же скудны, как они могут быть.

Во-первых, double.CompareTo(T) документация - это упорядочение четко определено в соответствии с документацией

Меньше нуля:   Этот экземпляр меньше значения.   -или-   Этот экземпляр не является числом (NaN), а значением является число.

     

Ноль   Этот экземпляр равен значению.   -или-   И этот экземпляр и значение не являются числом (NaN), PositiveInfinity или NegativeInfinity.

     

Больше нуля:   Этот экземпляр больше значения.   -или-   Этот экземпляр - это число и значение, а не число (NaN).

В LINQPad (3.5 и 4 оба имеют одинаковые результаты):

0d.CompareTo(0d).Dump();                  // 0
double.NaN.CompareTo(0d).Dump();          // -1
double.NaN.CompareTo(double.NaN).Dump();  // 0
0d.CompareTo(double.NaN).Dump();          // 1

Использование CompareTo(object) имеет те же результаты:

0d.CompareTo((object)0d).Dump();                  // 0
double.NaN.CompareTo((object)0d).Dump();          // -1
double.NaN.CompareTo((object)double.NaN).Dump();  // 0
0d.CompareTo((object)double.NaN).Dump();          // 1

Так что не проблема.

Теперь из Array.Sort(object[]) документация - не используется >, < или == (согласно документации) - просто CompareTo(object).

Сортирует элементы во всем одномерном массиве, используя реализацию IComparable каждого элемента массива.

Аналогично, Array.Sort(T[]) использует CompareTo(T).

Сортирует элементы во всем массиве, используя реализацию универсального интерфейса IComparable (Of T) каждого элемента массива.

Посмотрим:

LINQPad (4):

var ar = new double[] {double.NaN, 0, 1, double.NaN};
Array.Sort(ar);
ar.Dump();
// NaN, NaN, 0, 1

LINQPad (3.5):

var ar = new double[] {double.NaN, 0, 1, double.NaN};
Array.Sort(ar);
ar.Dump();
// NaN, 0, NaN, 1

LINQPad (3.5) - ПРИМЕЧАНИЕ. МАССА ОБЪЕКТА, и поведение "ожидается" в контракте CompareTo.

var ar = new object[] {double.NaN, 0d, 1d, double.NaN};
Array.Sort(ar);
ar.Dump();
// NaN, NaN, 0, 1

Хм. В самом деле. В заключение:

У меня нет ИДЕИ.

Счастливое кодирование.

Ответ 2

Я считаю, что, поскольку

a < NaN == false
a > NaN == false
a == NaN == false

поэтому сравнение с ними ломается, и это отбрасывает весь вид.

Ответ 3

Концептуально NaN не является числом, поэтому сравнение с числом не имеет смысла, следовательно:

a < NaN = false for all a,
a > NaN = false for all a and
NaN != NaN (!)

Чтобы решить эту проблему, вам нужно написать свой собственный компаратор, который использует IsNaN, чтобы сделать NaNs меньше (или больше) всех чисел, чтобы все они отображались на одном конце сортировки.

Изменить: Здесь образец версии сравнения:

class Program
{
    private static int NaNComparison(double first, double second)
    {
        if (double.IsNaN(first))
        {
            if (double.IsNaN(second)) // Throws an argument exception if we don't handle both being NaN
                return 0;
            else
                return -1;
        }
        if (double.IsNaN(second))
            return 1;

        if (first == second)
            return 0;
        return first < second ? -1 : 1;
    }

    static void Main(string[] args)
    {
        var doubles = new[] { double.NaN, 2.0, 3.0, 1.0, double.NaN };

        Array.Sort(doubles, NaNComparison);
    }
}

Ответ 4

Собственно, странное поведение сортировки является результатом ошибки в .NET 3.5. Ошибка была решена с помощью .NET 4.0.

Единственный способ разрешить это - использовать собственный пользовательский сопоставитель или перейти на .NET 4.0. См. Почему .NET 4.0 сортирует этот массив по-другому, чем .NET 3.5?

Ответ 5

поскольку вы используете сортировку по умолчанию, которая является алгоритмом QuickSort; реализация выполняет нестабильный вид; то есть, если два элемента равны, их порядок не может быть сохранен.