[英]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()
在left
和right
的變量也不能正確地排序。 你明白為什么嗎?
例
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.