我有一个大约30kk元素的集合trends
。当我尝试在linqpad中执行以下代码时
trends.Take(count).Dump();
它运作正常。
但如果我添加sort:
trends.OrderByDescending(x => x.Item2).Take(count).Dump();
我收到System.OutOfMemoryException
我做错了什么?
答案 0 :(得分:3)
OrderByDescending
(或OrderBy
)在您尝试获取第一个元素时实现整个序列 - 它必须,否则您不可能知道第一个元素。它必须制作序列的副本(当然通常只是一堆引用)才能排序,所以如果原始序列是内存中的集合,你最终会得到两个副本它的。大概你没有足够的记忆力。
答案 1 :(得分:3)
您不必对整个集合进行排序,只需从中获取顶级count
元素即可。以下是此https://codereview.stackexchange.com/a/9777/11651的解决方案。
这个答案的关键点是It doesn't require all items to be kept in memory(for sorting)
再次从链接中的答案评论:
这个想法是:你可以在O(n)时间内找到List的Max(或Min)项。如果你将这个想法扩展到m项(问题中的5),你可以更快地获得顶部(或buttom)m项,然后对列表进行排序(只需在列表中的一次传递+保存5个已排序项的成本)
答案 2 :(得分:1)
这是另一种可能比原始LINQ更好的扩展方法(例如,对于少量选定的项目,它不应该爆炸)。像L.B.的解决方案一样,它应该是O(n)并且不会将所有项目保留在内存中:
public static class Enumerables
{
public static IEnumerable<T> TopN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count, IComparer<TV> comparer)
{
var qCount = 0;
var queue = new SortedList<TV, List<T>>(count, comparer);
foreach (var val in value)
{
var currTv = selector(val);
if (qCount >= count && comparer.Compare(currTv, queue.Keys[0]) <= 0) continue;
if (qCount == count)
{
var list = queue.Values[0];
if (list.Count == 1)
queue.RemoveAt(0);
else
list.RemoveAt(0);
qCount--;
}
if (queue.ContainsKey(currTv))
queue[currTv].Add(val);
else
queue.Add(currTv, new List<T> {val});
qCount++;
}
return queue.SelectMany(kvp => kvp.Value);
}
public static IEnumerable<T> TopN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count)
{
return value.TopN(selector, count, Comparer<TV>.Default);
}
public static IEnumerable<T> BottomN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count, IComparer<TV> comparer)
{
return value.TopN(selector, count, new ReverseComparer<TV>(comparer));
}
public static IEnumerable<T> BottomN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count)
{
return value.BottomN(selector, count, Comparer<TV>.Default);
}
}
// Helper class
public class ReverseComparer<T> : IComparer<T>
{
private readonly IComparer<T> _comparer;
public int Compare(T x, T y)
{
return -1*_comparer.Compare(x, y);
}
public ReverseComparer()
: this(Comparer<T>.Default)
{ }
public ReverseComparer(IComparer<T> comparer)
{
if (comparer == null) throw new ArgumentNullException("comparer");
_comparer = comparer;
}
}
还有一些测试:
[TestFixture]
public class EnumerablesTests
{
[Test]
public void TestTopN()
{
var input = new[] { 1, 2, 8, 3, 6 };
var output = input.TopN(n => n, 3).ToList();
Assert.AreEqual(3, output.Count);
Assert.IsTrue(output.Contains(8));
Assert.IsTrue(output.Contains(6));
Assert.IsTrue(output.Contains(3));
}
[Test]
public void TestBottomN()
{
var input = new[] { 1, 2, 8, 3, 6 };
var output = input.BottomN(n => n, 3).ToList();
Assert.AreEqual(3, output.Count);
Assert.IsTrue(output.Contains(1));
Assert.IsTrue(output.Contains(2));
Assert.IsTrue(output.Contains(3));
}
[Test]
public void TestTopNDupes()
{
var input = new[] { 1, 2, 8, 8, 3, 6 };
var output = input.TopN(n => n, 3).ToList();
Assert.AreEqual(3, output.Count);
Assert.IsTrue(output.Contains(8));
Assert.IsTrue(output.Contains(6));
Assert.IsFalse(output.Contains(3));
}
[Test]
public void TestBottomNDupes()
{
var input = new[] { 1, 1, 2, 8, 3, 6 };
var output = input.BottomN(n => n, 3).ToList();
Assert.AreEqual(3, output.Count);
Assert.IsTrue(output.Contains(1));
Assert.IsTrue(output.Contains(2));
Assert.IsFalse(output.Contains(3));
}
}