- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
在实现这个通用 merge sort 时, 作为一种 Code Kata ,我偶然发现了 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);
}
}
如果我删除 left
和 right
变量上的 .ToList()
,它无法正确排序。你知道为什么吗?
例子
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
Without .ToList()
[0]: 1 [1]: 2 [2]: 5 [3]: 7 [4]: 2
Edit
It was my stupid test that got me.
I tested it like this:
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
的结果,您会看到结果排序为 (example) .
那么问题出在哪里呢?正如您所怀疑的那样,您正在测试错误 - 当您调用 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
)并向其中添加整数。这部分并不懒惰,这就是您看到前几个元素排序的原因。接下来,您添加剩余的元素 - 但 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
关于c# - List<T> 和 IEnumerable 的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4545090/
我对这个错误很困惑: Cannot implicitly convert type 'System.Func [c:\Program Files (x86)\Reference Assemblies\
考虑这段代码: pub trait Hello { fn hello(&self); } impl Hello for Any { fn hello(&self) {
问题很简单。是否可以构造这样一个类型 T,对于它下面的两个变量声明会产生不同的结果? T t1 = {}; T t2{}; 我已经研究 cppreference 和标准一个多小时了,我了解以下内容:
Intellij idea 给我这个错误:“Compare (T, T) in Comparator cannot be applied to (T, T)” 对于以下代码: public class
任何人都可以告诉我 : n\t\t\t\t\n\t\t\t 在以下来自和 dwr 服务的响应中的含义和用途是什么. \r\n\t\t\t \r\n\t\t\t
让 T 成为一个 C++ 类。 下面三个指令在行为上有什么区别吗? T a; T a(); T a = T(); T 为不带参数的构造函数提供了显式定义这一事实是否对问题有任何改变? 后续问题:如果
Rust中的智能指针是什么 智能指针(smart pointers)是一类数据结构,是拥有数据所有权和额外功能的指针。是指针的进一步发展 指针(pointer)是一个包含内存地
比如我有一个 vector vector > v={{true,1},{true,2},{false,3},{false,4},{false,5},{true,6},{false,7},{true,8
我有一个来自 .xls 电子表格的数据框,我打印了 print(df.columns.values) 列,输出包含一个名为:Poll Responses\n\t\t\t\t\t。 我查看了 Excel
This question already has answers here: What are good reasons for choosing invariance in an API like
指针类型作为类型前缀与在类型前加斜杠作为后缀有什么区别。斜线到底是什么意思? 最佳答案 语法 T/~ 和 T/& 基本上已被弃用(我什至不确定编译器是否仍然接受它)。在向新向量方案过渡的初始阶段,[T
我正在尝试找到一种方法来获取模板参数的基类。 考虑以下类: template class Foo { public: Foo(){}; ~Foo(){};
这是一个让我感到困惑的小问题。我不知道如何描述它,所以只看下面的代码: struct B { B() {} B(B&) { std::cout ::value #include
为什么有 T::T(T&) 而 T::T(const T&) 更适合 copy ? (大概是用来实现move语义的???) 原始描述(被melpomene证明是错误的): 在C++11中,支持了一种新
在 Java 7 中使用 eclipse 4.2 并尝试实现 List 接口(interface)的以下方法时,我收到了警告。 public T[] toArray(T[] a) { ret
假设有三个函数: def foo[T](a:T, b:T): T = a def test1 = foo(1, "2") def test2 = foo(List(), ListBuffer()) 虽
我对柯里化(Currying)和非柯里化(Currying)泛型函数之间类型检查的差异有点困惑: scala> def x[T](a: T, b: T) = (a == b) x: [T](a: T,
考虑一个类A,我如何编写一个具有与相同行为的模板 A& pretty(A& x) { /* make x pretty */ return x; } A pretty(A&& x) {
Eclipse 表示由于泛型类型橡皮擦,类型参数不允许使用 instanceof 操作。 我同意在运行时不会保留任何类型信息。但是请考虑以下类的通用声明: class SomeClass{ T
在 C++14 中: 对于任何整数或枚举类型 T 以及对于任何表达式 expr: 有没有区别: struct S { T t { expr }; }; 和 struct S { T t = { exp
我是一名优秀的程序员,十分优秀!