我有一个二进制文件,其中包含超过1亿个对象,我使用BinaryReader
读取文件并返回(Yield
)对象(文件阅读器和IEnumerable
实现在这里:Performance comparison of IEnumerable and raising event for each item in source?)
对象的一个属性表示对象排名(如A5
)。假设我想根据属性获取已排序的top n
个对象。
我看到OrderBy
函数的代码:它使用QuickSort算法。我尝试使用IEnumerable
和OrderBy
函数对Take(n)
结果进行排序,但我得到了OutOfMemory
异常,因为OrderBy
函数创建了一个大小为total的数组对象计数实现Quicksort。
实际上,我需要的总内存是 n ,所以不需要创建大数组。例如,如果我得到Take(1000),它将只返回1000个对象,并且它不依赖于整个对象的总数。
如何使用OrderBy
函数获取Take
函数的结果?换句话说,我需要一个有限或阻塞的排序列表,其容量由最终用户定义。
答案 0 :(得分:1)
如果你想要使用默认LINQ运算符的有序源中的前N个,那么只有选项将所有项加载到内存中,对它们进行排序并选择前N个结果:
items.Sort(condition).Take(N) // Out of memory
如果您只想排序前N个项目,那么只需先取项目,然后对它们进行排序:
items.Take(N).Sort(condition)
更新您可以使用缓冲区来保留N max个订购商品:
public static IEnumerable<T> TakeOrdered<T, TKey>(
this IEnumerable<T> source, int count, Func<T, TKey> keySelector)
{
Comparer<T, TKey> comparer = new Comparer<T,TKey>(keySelector);
List<T> buffer = new List<T>();
using (var iterator = source.GetEnumerator())
{
while (iterator.MoveNext())
{
T current = iterator.Current;
if (buffer.Count == count)
{
// check if current item is less than minimal buffered item
if (comparer.Compare(current, buffer[0]) <= 0)
continue;
buffer.Remove(buffer[0]); // remove minimual item
}
// find index of current item
int index = buffer.BinarySearch(current, comparer);
buffer.Insert(index >= 0 ? index : ~index, current);
}
}
return buffer;
}
此解决方案还使用项目的自定义比较器(按键比较):
public class Comparer<T, TKey> : IComparer<T>
{
private readonly Func<T, TKey> _keySelector;
private readonly Comparer<TKey> _comparer = Comparer<TKey>.Default;
public Comparer(Func<T, TKey> keySelector)
{
_keySelector = keySelector;
}
public int Compare(T x, T y)
{
return _comparer.Compare(_keySelector(x), _keySelector(y));
}
}
样本用法:
string[] items = { "b", "ab", "a", "abcd", "abc", "bcde", "b", "abc", "d" };
var top5byLength = items.TakeOrdered(5, s => s.Length);
var top3byValue = items.TakeOrdered(3, s => s);
答案 1 :(得分:1)
LINQ没有内置类,可以让你在没有将整个集合加载到内存中的情况下获取顶级n
元素,但你可以自己构建它。
一种简单的方法是使用SortedDictionary
列表:继续向其添加元素,直到达到n
的限制。之后,检查您要添加的每个元素以及到目前为止找到的最小元素(即dict.Keys.First()
)。如果新元素较小,则将其丢弃;否则,删除最小的元素,并添加一个新元素。
在循环结束时,您的排序字典将包含最多n
个元素,并且它们将根据您在字典上设置的比较器进行排序。