Как упорядочить или отсортировать целочисленный список и выбрать N-й элемент - PullRequest
0 голосов
/ 06 июня 2018

У меня есть список, и я хочу выбрать из него пятый по величине элемент:

List<int> list = new List<int>();

list.Add(2);
list.Add(18);
list.Add(21);
list.Add(10);
list.Add(20);
list.Add(80);
list.Add(23);
list.Add(81);
list.Add(27);
list.Add(85);

Но OrderbyDescending не работает для этого int списка ...

Ответы [ 6 ]

0 голосов
/ 06 июня 2018

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

Однако для больших наборов данных может быть намного быстрее сделать то, что известно как Partial Sort.

Есть два основных способа сделать это: использовать кучу или использовать специальную быструю сортировку.

В статье, которую я связал, описывается, как использовать кучу.Ниже я приведу частичную сортировку:

public static IList<T> PartialSort<T>(IList<T> data, int k) where T : IComparable<T>
{
    int start = 0;
    int end = data.Count - 1;

    while (end > start)
    {
        var index = partition(data, start, end);
        var rank = index + 1;

        if (rank >= k)
        {
            end = index - 1;
        }
        else if ((index - start) > (end - index))
        {
            quickSort(data, index + 1, end);
            end = index - 1;
        }
        else
        {
            quickSort(data, start, index - 1);
            start = index + 1;
        }
    }

    return data;
}

static int partition<T>(IList<T> lst, int start, int end) where T : IComparable<T>
{
    T x = lst[start];
    int i = start;

    for (int j = start + 1; j <= end; j++)
    {
        if (lst[j].CompareTo(x) < 0) // Or "> 0" to reverse sort order.
        {
            i = i + 1;
            swap(lst, i, j);
        }
    }

    swap(lst, start, i);
    return i;
}

static void swap<T>(IList<T> lst, int p, int q)
{
    T temp = lst[p];
    lst[p] = lst[q];
    lst[q] = temp;
}

static void quickSort<T>(IList<T> lst, int start, int end) where T : IComparable<T>
{
    if (start >= end)
        return;

    int index = partition(lst, start, end);
    quickSort(lst, start, index - 1);
    quickSort(lst, index + 1, end);
}

Затем, чтобы получить доступ к 5-му по величине элементу в списке, вы можете сделать это:

PartialSort(list, 5);
Console.WriteLine(list[4]);

Для больших наборов данных частичная сортировка можетбыть значительно быстрее, чем полная сортировка.


Приложение

См. здесь для другого (возможно, лучшего) решения, использующего Алгоритм быстрого выбора .

0 голосов
/ 06 июня 2018

Здесь есть реализация C # алгоритма QuickSelect для выбора n-го элемента в неупорядоченном IList<>.

Вы должны поместить весь код, содержащийся на этой странице, в static class, например:

public static class QuickHelpers
{
    // Put the code here
}

Учитывая, что «библиотека» (на самом деле большой жирный блок кода),тогда вы можете:

int resA = list.QuickSelect(2, (x, y) => Comparer<int>.Default.Compare(y, x));
int resB = list.QuickSelect(list.Count - 1 - 2);

Сейчас ... Обычно QuickSelect выбирает n-й низший элемент.Мы обращаем его двумя способами:

  • Для resA мы создаем обратный компаратор на основе стандартного int компаратора.Мы делаем это путем изменения параметров метода Compare.Обратите внимание, что индекс основан на 0.Таким образом, есть 0-й, 1-й, 2-й и т. Д.

  • Для resB мы используем тот факт, что 0-й элемент является list-1 -ым элементомв обратном порядке.Итак, мы рассчитываем со спины.Самый высокий элемент будет list.Count - 1 в упорядоченном списке, следующий list.Count - 1 - 1, затем list.Count - 1 - 2 и т. Д.

Теоретически использование Quicksort должно быть лучше, чем упорядочивание спискаи затем выбор n-го элемента, потому что упорядочение списка в среднем является операцией O (NlogN), а выбор n-го элемента является тогда операцией O (1), поэтому составной является операция O (NlogN), тогда как QuickSelect в среднем aO (N) операция.Очевидно, что есть, но.Обозначение O не показывает коэффициент k ... Так что O (k1 * NlogN) с небольшим k1 может быть лучше, чем O (k2 * N) с большим k2.Только несколько реальных тестов могут сказать нам (вам), что лучше, и это зависит от размера коллекции.

Небольшое примечание об алгоритме:

Как и в случае быстрой сортировкиБыстрый выбор обычно реализуется как алгоритм на месте, и помимо выбора k-го элемента, он также частично сортирует данные.См. Алгоритм выбора для дальнейшего обсуждения связи с сортировкой.

Таким образом, он изменяет порядок оригинала list.

0 голосов
/ 06 июня 2018

Этот LINQ подход извлекает 5-й по величине элемент ИЛИ создает исключение КОГДА список равен нулю или содержит менее 5 элементов:

int fifth = list?.Count >= 5 ?
    list.OrderByDescending(x => x).Take(5).Last() :
    throw new Exception("list is null OR has not enough elements");


Этот извлекает 5-й по величине элемент ИЛИ null КОГДА список null или содержит менее 5 элементов:

int? fifth = list?.Count >= 5 ?
    list.OrderByDescending(x => x).Take(5).Last() :
    default(int?);

if(fifth == null) // define behavior


Этот извлекает 5-й по величине элемент ИЛИ наименьший элемент КОГДА список содержит менее 5 элементов:

if(list == null || list.Count <= 0)
    throw new Exception("Unable to retrieve Nth biggest element");

int fifth = list.OrderByDescending(x => x).Take(5).Last();


Все эти решения надежны, они должны НИКОГДА не создавать "неожиданные" исключения.

PS : я использую .NET 4.7 в этомответить.

0 голосов
/ 06 июня 2018

В зависимости от серьезности списка, не имеющего более 5 элементов, у вас есть 2 варианта.

Если список никогда не должен превышать 5, я бы поймал его как исключение:

int fifth;
try
{
    fifth = list.OrderByDescending(x => x).ElementAt(4);
}
catch (ArgumentOutOfRangeException)
{
    //Handle the exception
}

Если вы ожидаете, что это будет менее 5 элементов, то вы можете оставить его по умолчанию и проверить его на это.

int fifth = list.OrderByDescending(x => x).ElementAtOrDefault(4);

if (fifth == 0)
{
    //handle default
}

Это все еще кое-что ошибочное, потому что вы можете получить пятый элементбыть 0. Это можно решить, вставив список в список пустых чисел до linq:

var newList = list.Select(i => (int?)i).ToList();
int? fifth = newList.OrderByDescending(x => x).ElementAtOrDefault(4);

if (fifth == null)
{
    //handle default
}
0 голосов
/ 06 июня 2018

Без LINQ выражений:

int result;
if(list != null && list.Count >= 5)
{
    list.Sort();
    result = list[list.Count - 5];
}
else // define behavior when list is null OR has less than 5 elements

Это имеет лучшую производительность по сравнению с выражениями LINQ, хотя решения LINQ , представленные в моем втором ответе, удобны инадежный.

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

Внимание : список изменяется при вызове метода Sort().Если вы этого не хотите, вы должны работать с копией вашего списка, например:

List<int> copy = new List<int>(original);

List<int> copy = original.ToList();
0 голосов
/ 06 июня 2018
int fifth = list.OrderByDescending(x => x).Skip(4).First();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...