Изучение LINQ: QuickSort

Сегодня днем ​​я сделал решительный шаг и начал изучать LINQ, пока просто возился с LINQ над коллекциями. Одним из первых, что я попробовал, было реализовать QSort.

Теперь - игнорируя тот факт, что я мог просто использую ORDERBY и что это очень глупая реализация qsort - я придумал следующее:

public class lqsort
{
    public static List<int> QSLinq(List<int> _items)
    {
        if (_items.Count <= 1)
            return _items;

        int _pivot = _items[0];

        List<int> _less = (from _item in _items where _item < _pivot select _item).ToList();
        List<int> _same = (from _item in _items where _item == _pivot select _item).ToList();
        List<int> _greater = (from _item in _items where _item > _pivot select _item).ToList();

        return (QSLinq(_less).Concat(_same.Concat(QSLinq(_greater)))).ToList();
    }
}

Единственное, что меня действительно беспокоит, - это кастинг. Могу ли я использовать какие-нибудь уловки LINQ? Или я просто использую LINQ для того, для чего он не предназначен?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
10
0
3 518
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Как насчет этого? (Если я хорошо понимаю, вам не нравятся вызовы .ToList)

public static IEnumerable<int> QSLinq(IEnumerable<int> items)
{
    if (items.Count() <= 1)
        return items;

    int pivot = items.First();

    return QSLinq(items.Where(i => i < pivot))
        .Concat(items.Where(i => i == pivot))
        .Concat(QSLinq(items.Where(i => i > pivot)));
}

Отказ от ответственности на основании Джон ответ: НЕ используйте этот алгоритм в реальной проблеме. Это очень неэффективно.

К сожалению, это все еще очень неэффективно. Возьмем рекурсивный вызов QSLinq «меньше» - ему придется перебирать свой параметр три раза, по одному разу для каждого вызова Where. Итерирование по ним означает повторение по исходному списку. То же самое и с рекурсивным вызовом "более чем".

Jon Skeet 09.10.2008 02:24

(Продолжение) Теперь рекурсивный вызов «меньше чем» на втором уровне рекурсии должен перебирать результаты первого уровня рекурсии и т. д. В основном он заканчивается итерацией по списку ужасное количество раз. Я слишком сонный, чтобы разбираться в сложности, но это мерзко.

Jon Skeet 09.10.2008 02:26

Я согласен, Джон, но проблема не в эффективности. Никто не собирается использовать этот алгоритм. Мы всегда можем использовать .OrderBy () (который, я надеюсь, не использует это :)

Panos 09.10.2008 02:29

Если эффективность не имеет значения, то у меня нет проблем с этим :) Я думаю, что стоит учесть, что эффективность Почему очень сложно определить при просмотре последовательностей.

Jon Skeet 09.10.2008 02:32

«НЕ используйте этот алгоритм в реальной проблеме. Он очень неэффективен». Очень неэффективно сказано, это сложность O (n ^ 3 * log (n)) :)

Pop Catalin 09.10.2008 05:12

Поскольку items является IEnumerable, было бы намного лучше использовать Any () вместо Count ().

Amy B 09.10.2008 06:30

Все задействовано в кастинге? Я не вижу кастингов. То, что я вижу делать, - это вызовы ToList, которые ужасно неэффективны. В основном LINQ предназначен для работы с последовательностями, которые по сути не позволяют вам работать на месте (или в дублированном пространстве) так, как это обычно делается при быстрой сортировке. По сути, здесь происходит очень много копирования данных :(

Да, я должен был сказать преобразование типов вместо приведения. Спасибо за советы. Я знал, что убиваю linq, но сейчас в основном играю с синтаксисом.

Dana 09.10.2008 03:37
Ответ принят как подходящий

Просто измените тип параметра на IEnumerable и используйте конструкцию var вместо List<int> для ваших локальных переменных.

Это улучшит ваш метод QSLinq, поскольку он будет принимать больше типов параметров, например int[], а также List<int>.

Смотрите новый метод:

    public static IEnumerable<int> QSLinq(IEnumerable<int> _items)
    {
        if (_items.Count() <= 1)
            return _items;

        var _pivot = _items.First();

        var _less = from _item in _items where _item < _pivot select _item;
        var _same = from _item in _items where _item == _pivot select _item;
        var _greater = from _item in _items where _item > _pivot select _item;

        return QSLinq(_less).Concat(QSLinq(_same)).Concat(QSLinq(_greater));
    }

Надеюсь это поможет.

Это худшее - пока я набирал ответ, Панос завершил и опубликовал свой ответ - точно такой же, как мой :-(

Alfred B. Thordarson 09.10.2008 02:21

Есть ли какая-либо разница, кроме синтаксиса, между этим явным представлением LINQ и лямбда-выражениями в версии Panos? Один из них более эффективен, чем другой?

Skeolan 09.10.2008 02:45

Я собираюсь принять этот ответ, потому что это именно то, что я хотел. Но другие ответы были действительно классными!

Dana 09.10.2008 19:30

Интересный вопрос! Это не превосходит OrderBy, но несколько ограничивает повторные перечисления.

    public static IEnumerable<int> QSort2(IEnumerable<int> source)
    {
        if (!source.Any())
            return source;
        int first = source.First();
        return source
            .GroupBy(i => i.CompareTo(first))
            .OrderBy(g => g.Key)
            .SelectMany(g => g.Key == 0 ? g : QSort2(g));
    }

Я случайно сгенерировал stackoverflow во время разработки, так как QSorted, когда Key == 0.


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

IEnumerable<int> source = Enumerable.Range(0, 1000).Reverse().ToList();
  • Решение тройного concat-where заняло 71000 миллисекунд.
  • Мое решение заняло 330 миллисекунд
  • OrderBy.ToArray занял 15 миллисекунд (разрешение таймера, поэтому фактическое время, вероятно, меньше)

Вот еще одно решение с использованием Aggregate. Я называю это: Группируйся и лови рыбу. Это занимает 170 мс в моем тесте на 1000 обратных элементов.

    public static IEnumerable<int> QSort3(IEnumerable<int> source)
    {
        if (!source.Any())
            return source;
        int first = source.First();

        QSort3Helper myHelper = 
          source.GroupBy(i => i.CompareTo(first))
          .Aggregate(new QSort3Helper(), (a, g) =>
              {
                  if (g.Key == 0)
                      a.Same = g;
                  else if (g.Key == -1)
                      a.Less = g;
                  else if (g.Key == 1)
                      a.More = g;
                  return a;
              });
        IEnumerable<int> myResult = Enumerable.Empty<int>();
        if (myHelper.Less != null)
            myResult = myResult.Concat(QSort3(myHelper.Less));
        if (myHelper.Same != null)
            myResult = myResult.Concat(myHelper.Same);
        if (myHelper.More != null)
            myResult = myResult.Concat(QSort3(myHelper.More));

        return myResult;
    }

    public class QSort3Helper
    {
        public IEnumerable<int> Less;
        public IEnumerable<int> Same;
        public IEnumerable<int> More;
    }

Почему это быстрее, чем мое решение, использующее OrderBy? Я думаю, это немного более устойчиво к худшему случаю.

Строка IEnumerable <int> myResult = Enumerable.Repeat (1, 0); можно заменить на Enumerable.Empty <int> ().

Patrik Hägne 20.10.2009 13:16

Выбранный ответ неверен, потому что он включает QSLinq (_same) вместо просто _same в возвращаемой коллекции и приводит к исключению StackOverflowException. Я буду использовать фиксированную версию в качестве контроля. Если решение может использовать копирование, скорость можно резко увеличить. Использование потоков вместо параллельного на самом деле немного снижает производительность при копировании вариантов. Использование потоков для вариантов без копирования немного увеличивает производительность. Отличие параллельной и некопирующей производительности от контроля незначительное.

all variants

copying variants

самое быстрое копирование:

private static List<int> quickie7(List<int> ites)
{
    if (ites.Count <= 1)
        return ites;
    var piv = ites[0];
    List<int> les = new List<int>();
    List<int> sam = new List<int>();
    List<int> mor = new List<int>();
    Enumerable.Range(0, 3).AsParallel().ForAll(i =>
    {
        switch (i)
        {
            case 0: les = (from _item in ites where _item < piv select _item).ToList(); break;
            case 1: sam = (from _item in ites where _item == piv select _item).ToList(); break;
            case 2: mor = (from _item in ites where _item > piv select _item).ToList(); break;
        }
    });
    var _les = new List<int>();
    var _mor = new List<int>();
    Enumerable.Range(0, 2).AsParallel().ForAll(i =>
    {
        switch (i)
        {
            case 0: _les = quickie7(les); break;
            case 1: _mor = quickie7(mor); break;
        }
    });
    List<int> allofem = new List<int>();
    allofem.AddRange(_les);
    allofem.AddRange(sam);
    allofem.AddRange(_mor);
    return allofem;
}

самый быстрый без копирования:

public static IEnumerable<int> QSLinq3(IEnumerable<int> _items)
{
    if (_items.Count() <= 1)
        return _items;
    var _pivot = _items.First();
    IEnumerable<int> _less = null;
    IEnumerable<int> _same = null;
    IEnumerable<int> _greater = null;
    ConcurrentBag<ManualResetEvent> finishes = new ConcurrentBag<ManualResetEvent>();
    Enumerable.Range(0, 3).AsParallel().ForAll(i =>
    {
        var fin = new ManualResetEvent(false);
        finishes.Add(fin);
        (new Thread(new ThreadStart(() =>
        {
            if (i == 0)
                _less = from _item in _items where _item < _pivot select _item;
            else if (i == 1)
                _same = from _item in _items where _item == _pivot select _item;
            else if (i == 2)
                _greater = from _item in _items where _item > _pivot select _item;
            fin.Set();
        }))).Start();
    });
    finishes.ToList().ForEach(k => k.WaitOne());
    return QSLinq(_less).Concat(_same).Concat(QSLinq(_greater));
}

Другие вопросы по теме