I’m trying to implement quicksort in a functional style using C# using linq, and this code randomly works/doesn’t work, and I can’t figure out why.
Important to mention: When I call this on an array or list, it works fine. But on an unknown-what-it-really-is IEnumerable, it goes insane (loses values or crashes, usually. sometimes works.)
The code:
public static IEnumerable<T> Quicksort<T>(this IEnumerable<T> source) where T : IComparable<T> { if (!source.Any()) yield break; var pivot = source.First(); var sortedQuery = source.Skip(1).Where(a => a.CompareTo(source.First()) <= 0).Quicksort() .Concat(new[] { pivot }) .Concat(source.Skip(1).Where(a => a.CompareTo(source.First()) > 0).Quicksort()); foreach (T key in sortedQuery) yield return key; }
Can you find any faults here that would cause this to fail?
Edit: Some better test code:
var rand = new Random(); var ienum = Enumerable.Range(1, 100).Select(a => rand.Next()); var array = ienum.ToArray(); try { array.Quicksort().Count(); Console.WriteLine("Array went fine."); } catch (Exception ex) { Console.WriteLine("Array did not go fine ({0}).", ex.Message); } try { ienum.Quicksort().Count(); Console.WriteLine("IEnumerable went fine."); } catch (Exception ex) { Console.WriteLine("IEnumerable did not go fine ({0}).", ex.Message); }
Some enumerable instances, such as those returned by Linq to SQL or Entity Framework queries, are only designed to be iterated once. Your code requires multiple iterations and will crash or behave strangely on these types of instances. You’d have to materialize those enumerables with
ToArray()or a similar method first.You should also be reusing that
pivotso that you don’t have to keep iterating for the first and remaining elements. This may not completely solve the problem, but it’ll help in some cases:(You also don’t need to iterate through the
sortedQuery– just return it, it’s already anIEnumerable<T>.)On a related note, why do you feel the need to re-implement this functionality?
Enumerable.OrderByalready does it for you.Response to update:
Your tests are failing because your test is wrong, not the algorithm.
Randomis a non-deterministic input source and, as I have explained above, the sort method needs to perform multiple iterations over the same sequence. If the sequence is totally random, then it is going to get different values on subsequent iterations. Essentially, you are trying to quicksort a sequence whose elements keep changing!If you want the test to succeed, you need to make the input consistent. Use a seed for the random number generator:
Then:
It will come back sorted.