具有大集合和OrderBy的OutOfMemoryException?

时间:2012-06-23 19:11:05

标签: c# .net out-of-memory

我有一个大约30kk元素的集合trends。当我尝试在linqpad中执行以下代码时

trends.Take(count).Dump();

它运作正常。

但如果我添加sort:

 trends.OrderByDescending(x => x.Item2).Take(count).Dump();

我收到System.OutOfMemoryException

我做错了什么?

3 个答案:

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