我正在尝试查看节点是否位于球体的体积内,并将节点ID添加到列表中 . 但是,算法的效率非常慢,我不知道如何改进它 .
我有两个清单 . 列表A的格式为 [{'num': ID, 'x': VALUE, 'y': VALUE, 'z': VALUE] ,而列表B的格式为 [{'x': VALUE, 'y': VALUE, 'z': VALUE, 'rad': VALUE}] .
两个列表的大小每个可以超过100,000个项目 .
我目前的代码发布在下面,但效率非常低 .
filteredList = []
for i in range(len(sList)):
minx = (sList[i]['x']) - (sList[i]['radius'])
maxx = (sList[i]['x']) + (sList[i]['radius'])
miny = (sList[i]['y']) - (sList[i]['radius'])
maxy = (sList[i]['y']) + (sList[i]['radius'])
minz = (sList[i]['z']) - (sList[i]['radius'])
maxz = (sList[i]['z']) + (sList[i]['radius'])
for j in range(len(nList)):
if minx <= nList[j]['x'] <= maxx:
if miny <= nList[j]['y'] <= maxy:
if minz <= nList[j]['z'] <= maxz:
tmpRad = findRadius(sList[i],nList[j])
if tmpRad <= sList[i]['radius']:
filteredList.append(int(nList[j]['num']))
我很茫然,欣赏任何想法 .
Edit: 添加有关数据格式的额外信息 .
List A (nList) - 定义具有位置x,y,z和标识符num的节点
[{_117_823:0.0,'x':0.0,'num':1.0,'z':0.0},
{'y':0.0,'x':1.0,'num':2.0,'z':0.0},
{'y':0.0,'x':2.0,'num':3.0,'z':0.0},
{'y':0.0,'x':3.0,'num':4.0,'z':0.0},
{'y':0.0,'x':4.0,'num':5.0,'z':0.0},
{'y':0.0,'x':5.0,'num':6.0,'z':0.0},
{'y':0.0,'x':6.0,'num':7.0,'z':0.0},
{'y':0.0,'x':7.0,'num':8.0,'z':0.0},
{'y':0.0,'x':8.0,'num':9.0,'z':0.0},
{'y':0.0,'x':9.0,'num':10.0,'z':0.0}]
List B (sList) - 使用x,y,z,radius定义球体
[{'y':18.0,'x':25.0,'z':26.0,'radius':0.0056470000000000001},
{'y':29.0,'x':23.0,'z':45.0,'radius':0.0066280000000000002},
{'y':46.0,'x':29.0,'z':13.0,'radius':0.014350999999999999},
{'y':0.0,'x':20.0,'z':25.0,'radius':0.014866000000000001},
{'y':27.0,'x':31.0,'z':18.0,'radius':0.018311999999999998},
{'y':10.0,'x':36.0,'z':46.0,'radius':0.024702000000000002},
{'y':27.0,'x':13.0,'z':48.0,'radius':0.027300999999999999},
{'y':14.0,'x':1.0,'z':13.0,'radius':0.033889000000000002},
{'y':31.0,'x':20.0,'z':11.0,'radius':0.034118999999999997},
{'y':23.0,'x':28.0,'z':8.0,'radius':0.036683}]
6 回答
(AFAICT,以下解决方案在算法上比到目前为止发布的任何其他答案更快:大约O(N log N)对O(N²) . 警告:这假设您在边界框之间没有大量重叠 . )
如果允许预先计算索引结构:
将所有最小/最大x值推入集合并对其进行排序,从而创建跨越x轴的垂直区域列表 . 将每个区域与包含它的一组边界框相关联 .
对min / max y值重复此过程,以创建水平区域列表,并将每个区域与其包含的边界框组相关联 .
对于每个被测试的点:
使用二进制印章查找包含点的x坐标的水平区域 . 但是,您真正想要的是与该区域相关联的一组边界框 .
同样,找到与y坐标关联的一组边界框 .
找到这两组的交集 .
使用毕达哥拉斯测试此残留集中的边界框 .
我们可以从样本中删除一个
除非您需要按索引迭代列表,否则不应该使用范围,并将ifs合并在一起
接受了所有这些建议后,我设法提出了比原始解决方案快50倍的解决方案 .
我意识到瓶颈在于我正在使用的数据类型(dicts列表) . 在我的演员阵容中循环使用多个列表非常慢,使用集合效率更高 .
我做的第一件事是实现命名元组 . 我知道我的节点列表是如何编号的,它提供了我需要的效率哈希 .
使用set()而不是list提供了大量的加速 . 数据集越大(nx,ny,nz),加速越快 .
它仍然可以使用树实现和域分解来改进,如同建议的那样,但目前它的工作原理 .
感谢大家的建议!
(这个答案涉及简单的优化和Python风格;它适用于现有算法,教授一些优化点,而不是用更有效的方法替换它 . )
以下是一些要点,以使代码更易于阅读和理解:
迭代sList,而不是超出范围(len(sList)) .
for i in range(len(sList))
变为for i in sList
并且sList[i]
变为i
.不需要那个tmpRad;把它内联 .
而不是
if a: if b: if c:
使用if a and b and c
.现在我们在这:
(PEP 8建议将该长行拆分为不超过80个字符的行; PEP 8还建议
filtered_list
和s_list
和n_list
而不是filteredList
,sList
和nList
. )我把
findRadius(i, j) <= i['radius']
放在第一位用于样式,因为看起来它可能更有可能被评估为false,从而加快了计算速度 . 然后我还内联了minx
等变量:要考虑的一件事是
i['x'] - i['radius'] <= j['x'] <= i['x'] + i['radius']
可以简化;尝试从所有三个部分中减去i['x']
.您可以通过列表理解来进一步缩短这一点 .
最后,named tuples(这有使其不可变的副作用,这可能是期望的吗?还要注意它只是Python 2.6,请阅读页面,了解如何使用旧版本的Python执行此操作):
然后,你可以做
sphere.radius
而不是sphere['radius']
. 这使得代码更整洁:或者,没有列表理解,
最后,选择更好的名字; [样式根据评论略有变化,最后将
findRadius
放在最后,因为它是最好的评判者,尽管如此]要么,
(如果需要,您可以将
srad = s.radius
放在外部循环中以获得可能的轻微性能增益 . )实际上,您可以通过以下方式保存所有内容:
如果从点到球体的球体的距离小于球体的半径,那么我想我们可以说它在球体中,对吧?
我假设findRadius定义如下:
首先,Python不是由低级语言教授的,它实际上更慢 .
range(len(whatever))
实际上创建了一个新的数字列表,然后您处理从该列表中传递给您的数字 . 你真正想做的就是使用从whatever
交给你的物品 .在我们处理它的同时,我们可以拉出多次检查的公共
s['radius']
位,并将边界框的所有if检查放在一行上 . 哦,我们不是't need a separate ' tmpRad ', and I assume the ' num已经是int
并且不需要转换(如果它们确实如此,为什么?为什么不提前转换它们?)这些都不会产生差异,但它至少使它更容易阅读,并且绝对不会受到伤害 .
现在它至少清楚发生了什么 .
但是,对于我们正在处理的问题的规模,听起来我们需要进行算法改进 . 你可能想要做的是使用某种BSP(二进制空间分区)技术 . 这种方式的工作原理是:
首先,我们将nList重新排列为树 . 我们将它切割成8个较小的列表,基于x> 0,是否y> 0以及每个点是否z> 0(3个布尔结果的8个组合) . 然后使用相同的标准 - 例如,每个都被切成8个 . 如果x / y / z的可能范围是-10..10,那么我们根据x> 5,y> 5,z> 5切割“x> 0,y> 0,z> 0”列表等等你明白了 .
对于sList中的每个点,我们检查minx> 0等 . 漂亮的部分:如果minx> 0,我们不必检查任何'x <0'列表,如果maxx <0,我们不必检查任何'x> 0'列表 . 等等 . 我们弄清楚边界框与之相交的空间的8个“八分圆”中的哪一个;对于每一个,我们递归检查那些八分圆等的适当的八分圆,直到我们到达树的叶子,然后我们做正常的入点边框,然后进行点球测试 .