理想情况下,您需要一种划分元素的方法,这样您就不必测试每一个元素来找到适合的元素和应该丢弃的元素。如何分区取决于项目的密集程度 - 例如,它可能就像在 X 坐标的整数部分上进行分区一样简单,或者通过该坐标的某个合适的缩放值进行分区。
给定一个方法(我们现在称之为Partition),它采用X 坐标并为其返回一个分区值,您可以在第一遍中快速过滤X 坐标以减少您需要检查的节点总数。不过,您可能需要稍微使用一下分区函数才能获得正确的分布。
例如,假设您在-100 < X <= 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 方法。还有其他容器类型可能更适合该任务。