问题 在Python中高效处理笛卡尔坐标列表


背景:

我正在编写一个程序来处理与各种规则形状的顶点网络相关的大量数据。我有一个工作的发生器,它根据一系列用户输入参数产生一个与所述形状的顶点相对应的笛卡尔坐标列表。然后将数据传递给过滤器,过滤器清除重复的条目,对数据和各种其他功能进行排序,从中将清理后的数据送到画布模块,画布模块循环并绘制顶点。

题:

我需要实现一个有效循环坐标的新过滤器,将每对与每对其他对比较,即 (x1,y1) - >(x2,y2) 至 (x1,y1) - >(xn,yn)(x2,y2) - >(x3,y3) 至 (x2,y2) - >(xn,yn) 等等,用于所有条目,例如,如果之间的关系 (x1,y1) 和 (x5,y5) 适合 [(x5-x1)^2+(y5-y1)^2]=vertex_spacing^2然后将两组坐标与它们各自的列表条目编号配对,并附加到一个新的列表中,其中一个条目的格式为: [(x1,y1), (x5,y5), 0, 4] 例如。实现这一目标的最有效方法是什么?

我的尝试:

我在这里和各种指南上看了很多处理列表的方法。我尝试嵌套'for'和'if'循环,但发现这个方法可以工作时会导致运行时间过长,并试图将问题分解成许多较小的for循环。

进一步说明:

这样做的最终目的是为前端界面元素使用生成的坐标,并根据需要进行保存和导入。列表的功能位置为0和4 [(x1,y1), (x5,y5), 0, 4] 是为了使接口能够对坐标进行分组,以便以后在canvas对象中使用。该方法应该能够处理数千个坐标。

提前感谢您的帮助,我当然愿意改进我提供的措辞/信息和/或添加示例代码,如果不清楚我在问什么 - 我还是很新的! :)


8453
2017-08-17 18:04


起源

祝贺一个问题很好的问题!问题是天真的方法(将每对与所有其他对比较)导致二次运行时间。你的积分如何分配?如果它们都在一个相对较小的空间(小的是大约的 vertex_spacing),这将是一个难题,否则你可以做一些简单的优化(我会根据分布将它们作为答案发布)。 - Vincent van der Weele
谢谢! :)我注意到了这种情况,我知道它会发生,虽然我想不出解决问题的办法。 vertex_spacing本身的值默认设置为60,但由于它是用户定义的,理论上它可以是任何大小。还使用vertex_spacing基于倍数和trig标识计算数据。实质上,vertex_spacing是我数据中的基本单位。我希望我明白你的意思,谢谢你的帮助! - MarkyD43
这是一个很好的问题。我在想一个决策树将是要走的路,我查看维基百科条目中的“最接近的点问题”,因为这似乎是背景的问题(?)的扩展,它看起来像一个好的决策树可以给你O(n log n)。在这种情况下这是真的吗? en.wikipedia.org/wiki/Closest_pair_of_points_problem - erewok
“我有一个工作的生成器,它根据一系列用户输入参数生成一个与所述形状的顶点对应的笛卡尔坐标列表“听起来你正在生成这些形状 - 你是否有可能只存储关于哪些顶点是边缘邻居的数据?而不是稍后计算它?只是一个想法。 - Brionius
@erewok a kd树 在分区谓词中可能更有用。 - msw


答案:


你基本上检查的是:

对于每个顶点 v,找到所有顶点 u 这样的 u 在半径圆上 vertex_spacing 周围 v

如果您的积分分布不是所有积分都很接近,我想到两种加速搜索的方法:

  1. 加速此过程的最简单方法是按x坐标对点进行排序。这样,您可以跳过许多比较。举个简单的例子,假设x坐标是 [1, 2, 10, 15, 18, 20, 21] 和 vertex_spacing = 5。您只需要比较第一个顶点和第二个顶点,因为所有其他顶点显然位于第一个顶点周围的圆圈之外。

    请注意,如果所有点都靠得很近,这种方法就没用了。换句话说,如果 vertex_spacing = 25,你不能跳过任何比较。

  2. 沿着同样的路线,你可以使用二维 k-d树。这相当于排序方法,但在两个方面。给定一个顶点 (x, y) 和 vertex_spacing = v,你必须检查范围内的所有点 ([x-v, x+v], [y-v, y+v])。使用与以前相同的示例,假设第一个点具有坐标 (1, 0) 和第二个 (2, 10),没有必要将第一个顶点与任何东西进行比较。

这两种方法都是启发式方法,并没有对最坏情况下的运行时间进行任何改进(完全相反:你也有排序/构建k-d树的开销),但如果顶点通常至少是 vertex_space 除此之外,这可以加快你的搜索速度。


6
2017-08-17 18:57





我太慢了,无法击败Heuster对算法的描述,但这里是按x排序方法的实现:

def pairs(coords, vertex_spacing):
    results = []
    vsquared = vertex_spacing * vertex_spacing
    coords = sorted(coords)
    for ia, (xa, ya) in enumerate(coords):
        for ib, (xb, yb) in enumerate(coords[ia:]):
            dx = xb - xa
            if dx > vertex_spacing:
                break
            dy = yb - ya
            if dx * dx + dy * dy == vsquared:
                results.append([(xa, ya), (xb, yb), ia, ia + ib])
    return results

......在这里它正在行动:

>>> coords = list((x, y) for x in range(100) for y in range(100))
>>> p = pairs(coords, 5)
>>> from random import choice
>>> choice(p)
[(93, 36), (96, 40), 9336, 9640]
>>> choice(p)
[(9, 57), (13, 54), 957, 1354]
>>> choice(p)
[(46, 69), (46, 74), 4669, 4674]

在我的机器上 pairs(coords, 5) 需要1.5秒来检查10,000个坐标对(和0.15秒来检查2,500个)。

编辑:我忘了添加 ia 至 ib 补偿在切片上的枚举 - 现在修复。


4
2017-08-17 19:08



+1我在Python中写的代码不够强大;-) - Vincent van der Weele
@Heuster发布后我就开始写类似的内容了,但这看起来已经死了所以我会批发它,我只是在我确认它作为回答之前完成调整,但是谢谢你们两个! - MarkyD43
@ MarkyD43请注意我刚才做的修复:-) - Zero Piraeus
这太棒了,谢谢!我想将两者都标记为已回答,但正如@Heuster的答案对于有类似问题的任何人来说都是一个很好的通用解决方案我会接受。再次感谢你! - MarkyD43
感谢您构建算法。我最初的想法是以相同的方式在两个for循环中使用枚举,以便比较一个点到 休息 这些点但我无法弄清楚算法的其余部分是如何工作的。再次感谢您花时间写出来。 - erewok


算法中最慢的部分是单独处理 X 和 ÿ 坐标和斜边计算。通过使用Python的本机复数类型可以加快这两个方面:

>>> from itertools import starmap
>>> parray = list(starmap(complex, [(5, 1), (8.5, 3), (3.75, 4.25)]))
>>> a = parray[0]
>>> b = parray[1]
>>> a
(5+1j)
>>> b
(8.5+3j)
>>> a-b
(-3.5-2j)
>>> abs(a-b)
4.031128874149275

3
2017-08-18 00:42





加快速度的一种方法是使用某种空间索引,这样就可以排除显然相距很远的搜索点。这是一个可能有用的模块: http://toblerity.org/rtree/。也可以看看 http://en.wikipedia.org/wiki/R-tree


1
2017-08-17 19:03