【问题标题】:I have a list with over a million objects in it, trying to find the fastest way to search through it我有一个包含超过一百万个对象的列表,试图找到最快的方法来搜索它
【发布时间】:2013-12-26 02:17:39
【问题描述】:

我有一个列表,其中存储了超过一百万个对象。我需要查看列表并尽可能高效地更新通过以下查询找到的对象。

我正在考虑使用 Dictionary 或 HashSet,但我对 C# 比较陌生,无法弄清楚如何实现这两种方法。我当前的代码只是一个通过 IList 搜索的 LINQ 语句。

public IList<LandObject> landObjects = new List<LandObject>();    

var lObjsToUpdate = from obj in landObjects 
                        where 
            obj.position.x >= land.x - size.x && 
            obj.position.x <= land.x + size.x && 
            obj.position.y >= land.y - size.y && 
            obj.position.y <= land.y + size.y
                    select obj;

 foreach(var item in lObjsToUpdate)
 {
     //do what I need to do with records I've found
 }

谁能建议我如何有效地解决这个问题?

【问题讨论】:

  • 我已经编辑了你的标题。请参阅“Should questions include “tags” in their titles?”,其中的共识是“不,他们不应该”。
  • 您要搜索什么样的查询?如果答案是一些非常有限的集合(或单一类型的查询),您可能可以优化数据结构以使其 O(1)
  • 我有一个正在显示的对象网格。 X 和 y 是代表其 Vector2 值的浮点数,我试图找到 x/y 值在一定范围内的对象。我需要找到整个对象来更新它的几个属性。
  • @Euthyphro,好的,这个计算是否超出了您用于查找对象的唯一标准?
  • @sinelaw,是的,没错。

标签: c# dictionary hash hashset ilist


【解决方案1】:

理想情况下,您需要一种划分元素的方法,这样您就不必测试每一个元素来找到适合的元素和应该丢弃的元素。如何分区取决于项目的密集程度 - 例如,它可能就像在 X 坐标的整数部分上进行分区一样简单,或者通过该坐标的某个合适的缩放值进行分区。

给定一个方法(我们现在称之为Partition),它采用X 坐标并为其返回一个分区值,您可以在第一遍中快速过滤X 坐标以减少您需要检查的节点总数。不过,您可能需要稍微使用一下分区函数才能获得正确的分布。

例如,假设您在-100 &lt; X &lt;= 100 范围内有浮点坐标,您的 1,000,000 多个对象在该范围内相当均匀地分布。如果按照X 的整数值进行分区,那么这会将列表分成(平均)5000 个条目的 200 个分区。这意味着对于搜索范围的 X 维度中的每个整数步长,您只有大约 5,000 个条目要测试。

这里有一些代码:

public interface IPosition2F
{
    float X { get; }
    float Y { get; }
}

public class CoordMap<T> where T : IPosition2F
{
    SortedDictionary<int, List<T>> map = new SortedDictionary<int,List<T>>();
    readonly Func<float, int> xPartition = (x) => (int)Math.Floor(x);

    public void Add(T entry)
    {
        int xpart = xPartition(entry.X);
        List<T> col;
        if (!map.TryGetValue(xpart, out col))
        {
            col = new List<T>();
            map[xpart] = col;
        }
        col.Add(entry);
    }

    public T[] ExtractRange(float left, float top, float right, float bottom)
    {
        var rngLeft = xPartition(left) - 1;
        var rngRight = xPartition(right) + 1;

        var cols =
            from keyval in map
            where keyval.Key >= rngLeft && keyval.Key <= rngRight
            select keyval.Value;

        var cells =
            from cell in cols.SelectMany(c => c)
            where cell.X >= left && cell.X <= right &&
                cell.Y >= top && cell.Y <= bottom
            select cell;

        return cells.ToArray();
    }

    public CoordMap()
    { }

    // Create instance with custom partition function
    public CoordMap(Func<float, int> partfunc)
    {
        xPartition = partfunc;
    }
}

这将在X 坐标上进行分区,从而减少您的最终搜索空间。如果您想更进一步,您还可以在Y 坐标上进行分区...我将把它作为练习留给读者:)

如果您的分区函数非常细粒度并且可能导致大量分区,则添加类似于以下内容的ColumnRange 函数可能会很有用:

    public IEnumerable<List<T>> ColumnRange(int left, int right)
    {
        using (var mapenum = map.GetEnumerator())
        {
            bool finished = mapenum.MoveNext();
            while (!finished && mapenum.Current.Key < left)
                finished = mapenum.MoveNext();

            while (!finished && mapenum.Current.Key <= right)
            {
                yield return mapenum.Current.Value;
                finished = mapenum.MoveNext();
            }

        }
    }

ExtractRange 方法可以像这样使用它:

    public T[] ExtractRange(float left, float top, float right, float bottom)
    {
        var rngLeft = xPartition(left) - 1;
        var rngRight = xPartition(right) + 1;

        var cells =
            from cell in ColumnRange(rngLeft, rngRight).SelectMany(c => c)
            where cell.X >= left && cell.X <= right &&
                cell.Y >= top && cell.Y <= bottom
            select cell;

        return cells.ToArray();
    }

为了方便起见,我使用了SortedDictionary,因为它可以实现相当快的ExtractRange 方法。还有其他容器类型可能更适合该任务。

【讨论】:

    【解决方案2】:

    可能有助于进行这么多迭代的一件事是进行一次计算并在查询中使用不同的变量。它还应该帮助一些人将范围每端增加 1 并消除检查等于:

    public IList<LandObject> landObjects = new List<LandObject>();    
    float xmax = land.x + size.x + 1;
    float xmin = land.x - size.x - 1;
    float ymax = land.y + size.y + 1;
    float ymin = land.y - size.y - 1;
    
    var lObjsToUpdate = from obj in landObjects 
                            where 
                obj.position.x > xmin && 
                obj.position.x < xmax && 
                obj.position.y > ymin && 
                obj.position.y < ymax
                        select obj;
    

    【讨论】:

      【解决方案3】:

      真正的答案应该涉及性能测试和比较,并且取决于您的运行时环境(内存与 cpu 等)。

      我会尝试的第一件事,因为 landsize 是常量,您可以维护一个简单的符合条件的对象 HashSet&lt;LandObject&gt;(除了所有对象的列表或集合或所有其他对象)对象)。每次添加新对象时,检查它是否符合标准,如果符合 - 将其添加到该组对象中。我不知道HashSet 在处理对象引用时在避免冲突方面有多好,但您可以尝试衡量一下​​。

      顺便说一句,这个related question 讨论HashSet&lt;int&gt; 的内存限制可能会让你感兴趣。使用 .NET HashSet 应该可以容纳数百万个项目。据我了解,通过某些配置,.NET 4.5 消除了 2gb 最大对象大小的限制,假设您有一台 64 位机器,您将能够发疯。

      【讨论】:

        猜你喜欢
        • 2018-05-11
        • 1970-01-01
        • 2013-05-11
        • 1970-01-01
        • 1970-01-01
        • 2010-11-11
        • 1970-01-01
        • 2015-04-02
        • 2023-03-19
        相关资源
        最近更新 更多