Сегодня днем я сделал решительный шаг и начал изучать 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 для того, для чего он не предназначен?





Как насчет этого? (Если я хорошо понимаю, вам не нравятся вызовы .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)));
}
Отказ от ответственности на основании Джон ответ: НЕ используйте этот алгоритм в реальной проблеме. Это очень неэффективно.
(Продолжение) Теперь рекурсивный вызов «меньше чем» на втором уровне рекурсии должен перебирать результаты первого уровня рекурсии и т. д. В основном он заканчивается итерацией по списку ужасное количество раз. Я слишком сонный, чтобы разбираться в сложности, но это мерзко.
Я согласен, Джон, но проблема не в эффективности. Никто не собирается использовать этот алгоритм. Мы всегда можем использовать .OrderBy () (который, я надеюсь, не использует это :)
Если эффективность не имеет значения, то у меня нет проблем с этим :) Я думаю, что стоит учесть, что эффективность Почему очень сложно определить при просмотре последовательностей.
«НЕ используйте этот алгоритм в реальной проблеме. Он очень неэффективен». Очень неэффективно сказано, это сложность O (n ^ 3 * log (n)) :)
Поскольку items является IEnumerable, было бы намного лучше использовать Any () вместо Count ().
Все задействовано в кастинге? Я не вижу кастингов. То, что я вижу делать, - это вызовы ToList, которые ужасно неэффективны. В основном LINQ предназначен для работы с последовательностями, которые по сути не позволяют вам работать на месте (или в дублированном пространстве) так, как это обычно делается при быстрой сортировке. По сути, здесь происходит очень много копирования данных :(
Да, я должен был сказать преобразование типов вместо приведения. Спасибо за советы. Я знал, что убиваю linq, но сейчас в основном играю с синтаксисом.
Просто измените тип параметра на 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));
}
Надеюсь это поможет.
Это худшее - пока я набирал ответ, Панос завершил и опубликовал свой ответ - точно такой же, как мой :-(
Есть ли какая-либо разница, кроме синтаксиса, между этим явным представлением LINQ и лямбда-выражениями в версии Panos? Один из них более эффективен, чем другой?
Я собираюсь принять этот ответ, потому что это именно то, что я хотел. Но другие ответы были действительно классными!
Интересный вопрос! Это не превосходит 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();
Вот еще одно решение с использованием 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> ().
Выбранный ответ неверен, потому что он включает 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));
}
К сожалению, это все еще очень неэффективно. Возьмем рекурсивный вызов QSLinq «меньше» - ему придется перебирать свой параметр три раза, по одному разу для каждого вызова Where. Итерирование по ним означает повторение по исходному списку. То же самое и с рекурсивным вызовом "более чем".