Изучение 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 для того, для чего он не предназначен?


person Dana    schedule 08.10.2008    source источник


Ответы (6)


Просто измените тип параметра на 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));
    }

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

person Alfred B. Thordarson    schedule 08.10.2008
comment
Это самое ужасное - пока я набирал ответ, Панос завершил и опубликовал свой ответ - точно такой же, как мой :-( - person Alfred B. Thordarson; 09.10.2008
comment
Есть ли какая-либо разница, кроме синтаксиса, между этим явным представлением LINQ и лямбда-выражениями в версии Panos? Один из них более эффективен, чем другой? - person Skeolan; 09.10.2008
comment
Я собираюсь принять этот ответ, потому что это именно то, что я хотел. Но другие ответы были действительно классными! - person Dana; 09.10.2008

Интересный вопрос! Это не превосходит 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 миллисекунд (разрешение таймера, поэтому фактическое время, вероятно, меньше)
person Amy B    schedule 09.10.2008

Как насчет этого? (Если я хорошо понимаю, вам не нравятся вызовы .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)));
}

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

person Panos    schedule 08.10.2008
comment
К сожалению, это все еще очень неэффективно. Возьмем менее чем рекурсивный вызов QSLinq - ему придется перебирать свой параметр три раза, по одному разу для каждого вызова Where. Итерирование по ним означает повторение по исходному списку. То же самое и с более чем рекурсивным вызовом. - person Jon Skeet; 09.10.2008
comment
(Продолжение) Теперь рекурсивный вызов less на втором уровне рекурсии должен перебирать результаты первого уровня рекурсии и т. Д. В основном он заканчивается итерацией по списку ужасное количество раз. Я слишком сонный, чтобы разбираться в сложности, но это мерзко. - person Jon Skeet; 09.10.2008
comment
Я согласен, Джон, но проблема здесь не в эффективности. Никто не собирается использовать этот алгоритм. Мы всегда можем использовать .OrderBy () (который, я надеюсь, не использует это :) - person Panos; 09.10.2008
comment
Если эффективность не имеет значения, тогда у меня нет проблем с этим :) Я думаю, что стоит подумать, почему эффективность так трудно достичь, глядя на последовательности. - person Jon Skeet; 09.10.2008
comment
НЕ используйте этот алгоритм в реальной проблеме. Это очень неэффективно. Очень неэффективно сказано, это сложность O (n ^ 3 * log (n)) :) - person Pop Catalin; 09.10.2008
comment
Поскольку items является IEnumerable, было бы намного лучше использовать Any () вместо Count (). - person Amy B; 09.10.2008

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

person Jon Skeet    schedule 08.10.2008
comment
Да, я должен был сказать преобразование типов вместо приведения. Спасибо за советы. Я знал, что убиваю linq, но сейчас я в основном играю с синтаксисом. - person Dana; 09.10.2008

Вот еще одно решение с использованием Aggregate. Я называю это: Group and Go Fish. Это занимает 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? Я думаю, это немного более устойчиво к худшему случаю.

person Amy B    schedule 09.10.2008
comment
Строка IEnumerable ‹int› myResult = Enumerable.Repeat (1, 0); можно заменить на Enumerable.Empty ‹int› (). - person Patrik Hägne; 20.10.2009

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

все варианты

копирование вариантов

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

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));
}
person fartwhif    schedule 23.02.2018