Подтвердить что ты не робот

Используя LINQ, чтобы взять верхние 100 и нижние 100?

Я хотел бы сделать что-то вроде этого (ниже), но не уверен, есть ли формальный/оптимизированный синтаксис для этого?

.Orderby(i => i.Value1)
.Take("Bottom 100 & Top 100")
.Orderby(i => i.Value2);

в принципе, я хочу отсортировать по одной переменной, затем взять верхние 100 и нижние 100, а затем отсортировать их по другой переменной.

Любые предложения?

4b9b3361

Ответ 1

var sorted = list.OrderBy(i => i.Value);
var top100 = sorted.Take(100);
var last100 = sorted.Reverse().Take(100);
var result = top100.Concat(last100).OrderBy(i => i.Value2);

Я не знаю, хотите ли вы Concat или Union в конце. Concat будет комбинировать все записи обоих списков, даже если есть похожие записи, которые будут иметь место, если ваш исходный список содержит менее 200 записей. Union добавит только материал из last100, который еще не находится в top100.

Некоторые вещи, которые не ясны, но которые следует учитывать:

  • Если list является IQueryable для db, вероятно, рекомендуется использовать ToArray() или ToList(), например.

    var sorted = list.OrderBy(i => i.Value).ToArray();
    

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

  • Метод Reverse не оптимизирован так, как я надеялся, но это не должно быть проблемой, так как упорядочение списка является реальной сделкой здесь. Для записи, однако, метод пропуска, объясненный в других ответах здесь, вероятно, немного быстрее, но ему нужно знать количество элементов в списке.

  • Если list будет LinkedList или другой класс, реализующий IList, метод Reverse может быть выполнен оптимизированным способом.

Ответ 2

Вы можете использовать метод расширения следующим образом:

public static IEnumerable<T> TakeFirstAndLast<T>(this IEnumerable<T> source, int count)
{
    var first = new List<T>();
    var last = new LinkedList<T>();
    foreach (var item in source)
    {
        if (first.Count < count)
            first.Add(item);
        if (last.Count >= count)
            last.RemoveFirst();
        last.AddLast(item);
    }

    return first.Concat(last);
}

(Я использую LinkedList<T> для last, потому что он может удалять элементы в O(1))

Вы можете использовать его следующим образом:

.Orderby(i => i.Value1)
.TakeFirstAndLast(100)
.Orderby(i => i.Value2);

Обратите внимание, что он не обрабатывает случай, когда имеется менее 200 элементов: если это случай, вы получите дубликаты. Вы можете удалить их, используя Distinct, если это необходимо.

Ответ 3

Возьмите верхние 100 и нижние 100 отдельно и соедините их:

var tempresults = yourenumerable.OrderBy(i => i.Value1);
var results = tempresults.Take(100);
results = results.Union(tempresults.Skip(tempresults.Count() - 100).Take(100))
                 .OrderBy(i => i.Value2);

Ответ 4

Вы можете сделать это в одном утверждении, используя эту .Where перегрузку, если у вас есть количество доступных элементов:

var elements = ...

var count = elements.Length; // or .Count for list

var result = elements
    .OrderBy(i => i.Value1)
    .Where((v, i) => i < 100 || i >= count - 100)
    .OrderBy(i => i.Value2)
    .ToArray();             // evaluate

Вот как это работает:

| first 100 elements | middle elements | last 100 elements |
        i < 100        i < count - 100    i >= count - 100

Ответ 5

Вы можете написать собственный метод расширения, например Take(), Skip() и другие методы из класса Enumerable. Он будет принимать количество элементов и общую длину в списке в качестве входных данных. Затем он возвращает первые и последние N элементы из последовательности.

var result = yourList.OrderBy(x => x.Value1)
                     .GetLastAndFirst(100, yourList.Length)
                     .OrderBy(x => x.Value2)
                     .ToList();

Вот способ расширения:

public static class SOExtensions
{
    public static IEnumerable<T> GetLastAndFirst<T>(
        this IEnumerable<T> seq, int number, int totalLength
    )
    {
        if (totalLength < number*2) 
            throw new Exception("List length must be >= (number * 2)");

        using (var en = seq.GetEnumerator())
        {
            int i = 0;

            while (en.MoveNext())
            {
                i++;
                if (i <= number || i >= totalLength - number) 
                     yield return en.Current;
            }
        }
    }
}