Резюме: при использовании Linq OrderBy с компаратором я вижу, что OrderBy сравнивает элементы сами с собой Compare (x, x), и я вижу, что он сравнивает одни и те же элементы Comparer (x, y) несколько раз.

  • Почему OrderBy Compare (x, x)?
  • Почему OrderBy сравнивает один и тот же товар несколько раз?

Описание проблемы

Если у вас есть (возможно, пустая) последовательность элементов и вам нужна самая большая последовательность, вы можете использовать OrderBy(...).FirstOrDefault().

Я подумал, что заказывать тысячи товаров будет пустой тратой вычислительной мощности, если вы будете использовать только самый крупный из них. Вы можете попытаться найти этот самый большой элемент в одном перечислении, создав какой-то метод Max.

Точно так же, если вы ищете несколько самых больших элементов: зачем заказывать все элементы?

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

Поэтому я решил создать тестовую программу, в которой я буду заказывать клиентов с помощью средства сравнения клиентов. Чтобы увидеть, какие клиенты являются компаратором, он записывает идентификаторы клиентов в консоль.

class Customer
{
    public int Id {get; set;}
    ...
}

class CustomerComparer : Comparer<Customer>
{
    public override int Compare(Customer x, Customer y)
    {
        int result = Comparer<int>.Default(x.Id, y.Id);
        Console.WriteLine("Compare {0} - {1} => {2}", x.Id, y.Id, result);
        return result;
    }
}

Консольная программа

static void Main(string[] args)
{
    var customers = new[]
    {
        new Customer {Id = 2},
        new Customer {Id = 9},
        new Customer {Id = 6},
        new Customer {Id = 1},
        new Customer {Id = 4},
        new Customer {Id = 7},
        new Customer {Id = 3},
        new Customer {Id = 8},
        new Customer {Id = 5},
    };

    IComparer<Customer> comparer = new CustomerComparer;
    var result = customers.OrderBy(customer => customer, customerComparer).FirstOrDefault();

Если я запускаю программу, я получаю следующий результат:

Compare 4 - 2 => 1
Compare 4 - 9 => -1
Compare 4 - 5 => -1
Compare 4 - 8 => -1
Compare 4 - 3 => 1
Compare 4 - 6 => -1
Compare 4 - 7 => -1
Compare 4 - 4 => 0
Compare 4 - 1 => 1
Compare 4 - 6 => -1
Compare 4 - 1 => 1
Compare 3 - 2 => 1
Compare 3 - 3 => 0
Compare 3 - 1 => 1
Compare 3 - 4 => -1
Compare 3 - 4 => -1
Compare 3 - 1 => 1
Compare 2 - 2 => 0
Compare 2 - 1 => 1
Compare 4 - 4 => 0
Compare 4 - 3 => 1
Compare 9 - 6 => 1
Compare 9 - 7 => 1
Compare 9 - 9 => 0
Compare 9 - 5 => 1
Compare 9 - 8 => 1
Compare 9 - 9 => 0
Compare 9 - 8 => 1
Compare 7 - 6 => 1
Compare 7 - 7 => 0
Compare 7 - 8 => -1
Compare 7 - 5 => 1
Compare 6 - 6 => 0
Compare 6 - 5 => 1
Compare 7 - 7 => 0
Compare 7 - 8 => -1
Compare 7 - 7 => 0

Я вижу некоторые странные вещи:

  • Заказчик [4] сравнивается сам с собой несколько раз. Это также для клиентов [7] и [6], но не для клиентов [8] и [1].
  • Клиент [4] сравнивается с Клиентом [6], и, спустя несколько сравнений, Клиент [4] снова сравнивается с Клиентом [6].
  • Клиенты [3] и [4] сравниваются дважды без каких-либо других сравнений между ними.
  • Двойное сравнение также для клиентов [4] и [1], а немного позже для [4] и [3], но не для других клиентов.

Почему это будет эффективный алгоритм сортировки?

0
Harald Coppoolse 18 Май 2021 в 13:55

1 ответ

Лучший ответ

Как упоминал Йерун Мостерт, вероятно, он сравнивает элементы с самими собой, чтобы упростить алгоритм, а простота в некоторых случаях может улучшить производительность. Я ожидал, что алгоритмы сортировки будут достаточно хорошо оптимизированы, поэтому я не буду беспокоиться о нескольких дополнительных сравнениях. Также обратите внимание, что Orderby гарантированно стабильна, и это может накладывать дополнительные ограничения на алгоритм.

Чтобы решить проблему возврата наибольшего значения, я бы предложил создать вашу собственную реализацию, которая выполняет итерацию по коллекции и возвращает наименьшее / наибольшее. Это довольно тривиально. Или используйте что-то вроде MoreLinq MaxBy / MinBy.

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

Внутренняя работа OrderBy не задокументирована. Теоретически среда выполнения может проверять всю последовательность вызовов linq и создавать оптимальный код. Изменить:

  • В .Net core 3.x и более поздних версиях это, похоже, оптимизируется до O(n) (спасибо Мэтью Уотсону за указание на это).
  • В структуре .Net похоже, что он создаст EnumerableSorter, который в конечном итоге выполнит быструю сортировку всего, предположительно стабильного варианта. т.е. O(n log n)
  • В структуре сущностей запрос должен быть переведен на SQL и запущен через оптимизатор запросов, что, вероятно, приведет к O(n) (или лучше) времени выполнения.
4
JonasH 18 Май 2021 в 11:52