C#功能快速排序失败
我想实现使用C#使用LINQ的功能风格的快速排序,并且此代码随机工作/不工作,我不明白为什么。
重要提醒:当我在数组或列表上调用它时,它工作正常。可是,在一个未知的 - 什么 - 它,真的,是IEnumerable的,它会疯狂(失去价值或崩溃,通常,有时作品。)
代码:C#功能快速排序失败
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; }
你能找到的任何故障这会导致这个失败?
编辑:一些更好的测试代码:
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); }
一些枚举情况下,如由返回的LINQ to SQL或实体框架查询,仅设计进行一次迭代。您的代码需要多次迭代,并且会在这些类型的实例上崩溃或行为异常。你必须首先用ToArray()
或类似的方法实现这些枚举。
您还应该重复使用该pivot
,以便您不必为第一个元素和其余元素进行迭代。这可能不会完全解决问题,但它会在某些情况下帮助:(您还没有通过sortedQuery
需要迭代 - 只返回它,它已经是一个IEnumerable<T>
)
public static IEnumerable<T> Quicksort<T>(this IEnumerable<T> source)
where T : IComparable<T>
{
if (!source.Any())
return source;
var pivot = source.First();
var remaining = source.Skip(1);
return remaining
.Where(a => a.CompareTo(pivot) <= 0).Quicksort()
.Concat(new[] { pivot })
.Concat(remaining.Where(a => a.CompareTo(pivot) > 0).Quicksort());
}
在相关说明中,为什么您觉得需要重新实现此功能? Enumerable.OrderBy
已经为你做。
响应更新:
你的测试失败,因为测试是错误,而不是算法。
Random
是一个非确定性的输入源,正如我上面所解释的,排序方法需要在同一个序列上执行多次迭代。如果序列是完全随机的,那么在随后的迭代中将得到不同的值。本质上,你正试图快速排序一个元素不断变化的序列!
如果您希望测试成功,您需要使输入一致。使用种子随机数发生器:
static IEnumerable<int> GetRandomInput(int seed, int length)
{
Random rand = new Random(seed);
for (int i = 0; i < length; i++)
{
yield return rand.Next();
}
}
然后:
static void Main(string[] args)
{
var sequence = GetRandomInput(248917, 100);
int lastNum = 0;
bool isSorted = true;
foreach (int num in sequence.Quicksort())
{
if (num < lastNum)
{
isSorted = false;
break;
}
lastNum = num;
}
Console.WriteLine(isSorted ? "Sorted" : "Not sorted");
Console.ReadLine();
}
它会回来的排序。
我的enumerable实际上只是Enumerable.Range,它仍然失败。 此外,我尝试只返回sortedQuery,但我得到一个错误 - “不能从一个迭代器中返回一个值。使用yield return语句返回一个值,或者产生break来结束迭代。” 而且 - 而且 - 我不需要实现这一点,我只是想尝试学习函数式编程。 – Rubys 2010-04-24 14:44:34
@Rubys:你说的“无法返回值”的错误是正确的 - 我刚刚解决了这个问题,那个问题就是开始时的“收益率突破”,并且最终与直接回报混合在一起。我会用'Enumerable.Range'来试试这个,看看会发生什么。 – Aaronaught 2010-04-24 14:53:07
@Rubys:在这里'Enumerable.Range'绝对正常。发布失败的测试代码。 – Aaronaught 2010-04-24 14:55:42
你是什么意思'未知 - 它真的是IEnumerable'?这是一个通用的方法,所以你的对象的类型总是已知的。 – 2010-04-24 14:32:17
我的意思是我不知道IEnumerable shell下面是什么。这是一个列表吗?数组?我尝试过的和失败的是从一个列表中,我基本上做了“Random rand = ...; int [100] .Select(a => rand.Next());” – Rubys 2010-04-24 14:41:03