繁体   English   中英

列出<T>和IEnumerable的区别

[英]List<T> and IEnumerable difference

在实现这种通用的合并排序时 ,作为一种Code Kata ,我偶然发现了IEnumerable和List之间的差异,我需要帮助弄清楚。

这是MergeSort

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()leftright的变量也不能正确地排序。 你明白为什么吗?

var ints = new List<int> { 5, 8, 2, 1, 7 };
var mergeSortInt = new MergeSort<int>();
var sortedInts = mergeSortInt.Sort(ints);

使用.ToList()

[0]: 1
    [1]: 2
    [2]: 5
    [3]: 7
    [4]: 8

没有.ToList()

[0]: 1
    [1]: 2
    [2]: 5
    [3]: 7
    [4]: 2

编辑

这是我的愚蠢测试让我。

我测试了这样:

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

我想我会弄清楚懒惰的评价是如何在这里弄乱的,但我只是不明白。

像这样删除上面的Sort方法中的.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()之前看到一个sortedInts.ToList()返回

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

但是在ints.Sort()它会返回

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

这里到底发生了什么?

您的函数是正确的 - 如果您检查Merge的结果,您将看到结果已排序(示例)
那么问题出在哪里? 正如您所怀疑的那样,您正在测试它是错误的 - 当您在原始列表上调用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!

您创建的所有集合与first集合基本相同 - 在某种程度上,它们是对ints位置的惰性指针。 显然,当您调用ToList ,问题就会消除。

你的情况比这更复杂。 您的Sort部分是懒惰的,完全按照您的建议:首先创建一个列表( arrSorted )并arrSorted添加整数。 那部分不是懒惰的,这也是你看到前几个元素排序的原因。 接下来,添加其余元素 - 但Concat是懒惰的。 现在,递归变得更加混乱:在大多数情况下, IEnumerable上的大多数元素都是渴望的 - 你可以创建左右列表,这些列表也主要是渴望+懒惰的尾巴。 最终得到一个排序的List<int> ,懒惰地连接到一个惰性指针,它应该只是最后一个元素 (其他元素之前被合并)。
这是你的函数的调用图 - 红色表示一个懒惰的集合,黑色表示一个实数:

替代文字

当您更改列表时,新列表基本上是完整的,但最后一个元素是惰性的,并指向原始列表中最大元素的位置。

结果大部分都很好,但它的最后一个元素仍然指向原始列表:

替代文字

最后一个例子:考虑您正在更改原始列表中的所有元素。 如您所见,已排序集合中的大多数元素保持不变,但最后一个是惰性并指向新值:

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 }

以下是Ideone上的相同示例: http ://ideone.com/FQVR7

无法重现 - 我刚试过这个,它的工作非常好。 显然,它在各种方面效率相当低,但删除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");
    }
}

输出:

Sorted
1 2 5 7 8

也许问题是你如何测试你的代码?

我运行它有和没有列表,它工作。
无论如何,合并排序的优势之一是它能够使用O(1)空间复杂度就地排序,这种实现不会受益。

问题是您排序左侧,而不是右侧并合并为一个序列。 这并不意味着你得到一个完全排序的序列。

首先,您必须合并,而不是必须排序:

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));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM