C# 列表<;T>;还有数不清的差别

C# 列表<;T>;还有数不清的差别,c#,generics,ienumerable,C#,Generics,Ienumerable,在实现这个泛型的过程中,我无意中发现了IEnumerable和List之间的一个差异,我需要帮助找出这个差异 这是我的分类 public class MergeSort<T> { public IEnumerable<T> Sort(IEnumerable<T> arr) { if (arr.Count() <= 1) return arr; int middle = arr.Count() / 2;

在实现这个泛型的过程中,我无意中发现了IEnumerable和List之间的一个差异,我需要帮助找出这个差异

这是我的分类

public class MergeSort<T>
{
    public IEnumerable<T> Sort(IEnumerable<T> arr)
    {
        if (arr.Count() <= 1) return arr;

        int middle = arr.Count() / 2;
        var left = arr.Take(middle).ToList();
        var right = arr.Skip(middle).ToList();
        return Merge(Sort(left), Sort(right));
    }

    private static IEnumerable<T> Merge(IEnumerable<T> left, IEnumerable<T> right)
    {
        var arrSorted = new List<T>();

        while (left.Count() > 0 && right.Count() > 0)
        {
            if (Comparer<T>.Default.Compare(left.First(), right.First()) < 0)
            {
                arrSorted.Add(left.First());
                left=left.Skip(1);
            }
            else
            {
                arrSorted.Add(right.First());  
                right=right.Skip(1);  
            }
        }

        return arrSorted.Concat(left).Concat(right);
    }
}
使用
.ToList()

编辑

这是我愚蠢的测试,让我

我是这样测试的:

var sortedInts = mergeSortInt.Sort(ints);
ints.Sort();
if (Enumerable.SequenceEqual(ints, sortedInts)) Console.WriteLine("ints sorts ok");
只是把第一行改成

var sortedInts = mergeSortInt.Sort(ints).ToList();
删除问题(以及延迟计算)

编辑2010-12-29


我想我会明白懒惰的评估是如何把事情搞砸的,但我就是不明白

在上面的排序方法中删除
.ToList()
,如下所示

var left = arr.Take(middle);
var right = arr.Skip(middle);
那就试试这个

var ints = new List<int> { 5, 8, 2 };
var mergeSortInt = new MergeSort<int>();
var sortedInts = mergeSortInt.Sort(ints);
ints.Sort();
if (Enumerable.SequenceEqual(ints, sortedInts)) Console.WriteLine("ints sorts ok");
但是在
ints.Sort()之后它返回

[0]: 2
[1]: 5
[2]: 5

这里到底发生了什么?

问题是你要按左右排序,而不是按右边排序,然后合并成一个序列。这并不意味着你得到了一个完全排序的序列

首先必须合并,然后必须排序:

public IEnumerable<T> Sort(IEnumerable<T> arr)
{
    if (arr.Count() <= 1) return arr;

    int middle = arr.Count() / 2;
    var left = arr.Take(middle).ToList();
    var right = arr.Skip(middle).ToList();

    // first merge and than sort
    return Sort(Merge(left, right));
}
公共IEnumerable排序(IEnumerable arr)
{

如果(arr.Count()我在有列表和没有列表的情况下运行了它,它就工作了。

无论如何,合并排序的优点之一是它能够使用O(1)进行就地排序空间复杂性,这一实现将不会受益。

无法复制-我刚刚尝试了这个,它工作得非常好。显然,它在各种方面都相当低效,但是删除
ToList
调用并没有使它失败

这是我的测试代码,您的
MergeSort
代码保持原样,但没有
ToList()
调用:

using System;
using System.Collections.Generic;

public static class Extensions
{
    public static void Dump<T>(this IEnumerable<T> items, string name)
    {
        Console.WriteLine(name);
        foreach (T item in items)
        {
            Console.Write(item);
            Console.Write(" ");
        }
        Console.WriteLine();
    }
}

class Test
{    
    static void Main()
    {
        var ints = new List<int> { 5, 8, 2, 1, 7 };
        var mergeSortInt = new MergeSort<int>();
        var sortedInts = mergeSortInt.Sort(ints);
        sortedInts.Dump("Sorted");
    }
}

可能问题在于您是如何测试代码的?

您的函数是正确的-如果您检查
合并的结果,您将看到结果已排序。
那么问题出在哪里呢?正如您所怀疑的,您的测试是错误的-当您在原始列表上调用
Sort
时,您会更改从它派生的所有集合!

下面是一个演示您所做工作的片段:

List<int> numbers = new List<int> {5, 4};
IEnumerable<int> first = numbers.Take(1);
Console.WriteLine(first.Single()); //prints 5
numbers.Sort();
Console.WriteLine(first.Single()); //prints 4!

在Ideone上也有同样的例子:

这完全违背了合并排序的目的-你也可以
排序(arr)
-你建议拆分和合并(=什么都不做),然后排序
:)
合并排序并不声称是O(1)复杂度。它是O(n log n)。它没有声明,但可以在O(1)中实现空间复杂度和O(nlgn)时间复杂度的一个小常数因子都是这里所缺少的。啊,你是说空间复杂度吗?我从没听说过“地点复杂度”以前。如果是这样,是的,那是真的。这在很多方面都是低效的,但我认为这是用于教育实验,这很好。必须说,在删除
ToList()
后,它在我的电脑上运行得很好。下面是另一个无法重现的示例(使用mono):我看到的唯一可疑部分是
left=left.Skip(1)
,这可能会有执行延迟的问题,但我不知道如何解决。我在问题中添加了一个新的示例,因为我仍然无法准确地了解这里发生了什么。我已经实现了一个完全懒惰的评估版本,我想我会弄清楚懒惰的评估是如何把事情搞砸的,但我不明白。我添加了一个新的示例这个问题的例子。我明白了。我还明白可变集合和惰性求值是一个糟糕的组合。我不明白的是,在这种情况下,它是如何扰乱排序的,因为已经排序的列表不应该是一个问题。除非我的小MergeSort不是完全惰性的,而是半惰性的。@Jonas-我已经用mor更新了我的解释e详细信息,您的代码中似乎不止一点复杂。
:)
精彩的解释!现在我终于明白了。谢谢!我实现了一个完全懒惰的评估版本,并在博客中介绍了它和我面临的问题。@Jonas-谢谢大家的关注,很高兴能提供帮助!
[0]: 2
[1]: 5
[2]: 5
public IEnumerable<T> Sort(IEnumerable<T> arr)
{
    if (arr.Count() <= 1) return arr;

    int middle = arr.Count() / 2;
    var left = arr.Take(middle).ToList();
    var right = arr.Skip(middle).ToList();

    // first merge and than sort
    return Sort(Merge(left, right));
}
using System;
using System.Collections.Generic;

public static class Extensions
{
    public static void Dump<T>(this IEnumerable<T> items, string name)
    {
        Console.WriteLine(name);
        foreach (T item in items)
        {
            Console.Write(item);
            Console.Write(" ");
        }
        Console.WriteLine();
    }
}

class Test
{    
    static void Main()
    {
        var ints = new List<int> { 5, 8, 2, 1, 7 };
        var mergeSortInt = new MergeSort<int>();
        var sortedInts = mergeSortInt.Sort(ints);
        sortedInts.Dump("Sorted");
    }
}
Sorted
1 2 5 7 8
List<int> numbers = new List<int> {5, 4};
IEnumerable<int> first = numbers.Take(1);
Console.WriteLine(first.Single()); //prints 5
numbers.Sort();
Console.WriteLine(first.Single()); //prints 4!
var ints = new List<int> { 3,2,1 };
var mergeSortInt = new MergeSort<int>();
var sortedInts = mergeSortInt.Sort(ints);
// sortedInts is { 1, 2, 3 }
for(int i=0;i<ints.Count;i++) ints[i] = -i * 10;
// sortedInts is { 1, 2, 0 }