Преобразовать LINQ orderby в сортировку по списку на месте

#c# #linq #list #sorting

#c# #linq #Список #сортировка

Вопрос:

В настоящее время я сортирую список с помощью LINQ to objects, а затем выполняю ToList() обработку результатов:

 var SortedPossibleMoveLocations = (from PML in PossibleMoveLocations
                                   orderby Randomiser.Next()
                                   orderby IsSameType(PML) ? (_Owner[PML] as TileFlowing).UnitsWithin : 0
                                   orderby PossibleMoveLocationOrdering(PML)
                                   select PML).ToList();
  

Я хочу преобразовать это для выполнения сортировки на месте, я думаю, используя List<T>.Sort() метод. Если бы я упорядочивал только по одной вещи, я бы знал, как это сделать, однако, поскольку я упорядочиваю по PossibleMoveLocationOrdering (который возвращает int ), затем по IsSameType(PML) ? (_Owner[PML] as TileFlowing).UnitsWithin : 0 , который вычисляется как int , затем по Randomiser.Next() (который возвращает случайное значение int), я не знаю, как это сделать.

Вопрос: Как мне написать функцию сравнения (или есть лучший метод), чтобы выполнить скрытую сортировку запроса LINQ выше.

Комментарии:

1. Обратите внимание, что множественный порядок почти всегда неправильный — это не добавит ThenBy , поэтому фактически инвертирует приоритет сортировки. Это должен быть один порядок с несколькими выражениями

2. Я понимаю, что я получу порядок, противоположный порядку x, y, z . (Хотя я написал это до того, как узнал о синтаксисе single orderby). Вот почему в тексте под кодом я уточняю, что имеет значение. Хотя спасибо за информацию.

Ответ №1:

Для начала, указание трех orderby предложений — плохая идея — вместо этого укажите несколько порядков, просто разделяя их запятой.

Я также не в восторге от идеи использования Randomiser.Next() для упорядочивания, но это в сторону.

Ваш запрос LINQ должен выглядеть следующим образом (на данный момент все еще с Randomiser in):

 var query = (from PML in PossibleMoveLocations
             orderby PossibleMoveLocationOrdering(PML),
                     IsSameType(PML) ? (_Owner[PML] as TileFlowing).UnitsWithin : 0,
                     Randomiser.Next()
             select PML).ToList();
  

Лично я бы просто использовал для этого точечную нотацию:

 var query = PossibleMoveLocations
                .OrderBy(pml => PossibleMoveLocationOrdering(PML))
                .ThenBy(pml => IsSameType(pml) ? 
                                    (_Owner[pml] as TileFlowing).UnitsWithin : 0)
                .ThenBy(pml => Randomiser.Next())
                .ToList();
  

Для сортировки на месте вам в основном нужен Comparison<T> or IComparer<T> , который может тестировать несколько вещей, а также реализация, которая создает средство сравнения с использованием свойств. Вы можете сделать это вручную (согласно коду Marc), но так получилось, что у меня есть несколько вспомогательных классов и методов расширения в MiscUtil:

 var comparer = ProjectionComparer<PossibleMove>
                   .Create(pml => PossibleMoveLocationOrdering(PML));
                   .ThenBy(pml => IsSameType(pml) ? ...)
                   .ThenBy(...);

list.Sort(comparer);
  

Обратите внимание, что использовать Randomizer здесь определенно плохая идея, поскольку она будет вызываться при каждом сравнении (для объектов с равными первыми частями)… это может привести к непоследовательному сравнению, такому что x < y < z < x .

Комментарии:

1. Какой была бы альтернатива orderby по случайному числу в конечном случае (все остальные значения order by равны)?

2. Кроме того, использование нескольких orderby — плохая идея исключительно из-за удобства чтения или есть другая причина?

3. @George: В принципе, в этом случае у вас нет согласованного порядка. Если вы не можете последовательно различать два объекта, почему бы просто не возвращать 0 при их сравнении?

4. @George: Использование нескольких orderby предложений будет намного менее эффективным — это приведет к изменению всего порядка (полагаясь на стабильность порядка для сохранения «предыдущих» порядков), а не просто к проверке тай-брейков. Существует также проблема с удобочитаемостью, связанная с необходимостью указывать все в обратном порядке…

5. @Jon: Поскольку я не хочу, чтобы порядок был таким же, как в исходном списке в этом случае, я хочу случайный порядок.

Ответ №2:

Чаще всего:

 list.Sort((x,y) => {
    int result = /* first comparison, for example
                    string.Compare(x.Name, y.Name) */
    if (result == 0) result = /* second comparison,
                                 for example x.Id.CompareTo(y.Id) */
    ...
    if (result == 0) result = /* final comparison */
    return resu<
});
  

или аналогичный (возможно, в классе сравнения, если это нетривиально).