想象一个离散的x,y,z空间:我正在尝试创建一个迭代器,它将返回位于距点一定径向距离的球体内的所有点。
我的方法是先看一个更大的立方体内的所有点,保证包含所需的所有点,然后剔除或跳过太远的点。
我的第一次尝试是:
x,y,z=(0,0,1)
dist=2
#this doesn't work
it_0=((x+xp,y+yp,z+zp) for xp in range(-dist,dist+1) for yp in range(-dist,dist+1) for zp in range(-dist,dist+1) if ( ((x-xp)**2+(y-yp)**2+(z-zp)**2) <= dist**2+sys.float_info.epsilon ) )
一个简单的
for d,e,f in it_0:
#print(d,e,f)
print( ((x-d)**2+(y-e)**2+(z-f)**2) <= dist**2+sys.float_info.epsilon, d,e,f)
验证it_0不会产生正确的结果。我相信它仅将条件应用于第三个(即:z)'for'子句
以下作品:
it_1=((x+xp,y+yp,z+zp) for xp in range(-dist,dist+1) for yp in range(-dist,dist+1) for zp in range(-dist,dist+1))
it_2=filter( lambda p: ((x-p[0])**2+(y-p[1])**2+(z-p[2])**2) <= dist**2+sys.float_info.epsilon, it_1)
它收集所有点,然后过滤那些不符合条件的点。
我希望可能有办法纠正第一次尝试的实现,或者使这些表达式更具可读性或紧凑性。
答案 0 :(得分:3)
你在生成器表达式中混淆了xp,yp,zp的含义:
it_0=((x+xp,y+yp,z+zp) for xp in range(-dist,dist+1) for yp in range(-dist,dist+1) for zp in range(-dist,dist+1) if ( ((x-xp)**2+(y-yp)**2+(z-zp)**2) <= dist**2+sys.float_info.epsilon ) )
xp,yp和zp已经是球体中心沿各个轴的距离。因此,你不应该再次从x,y,z中获取差异。表达式应为:
it_0=((x+xp,y+yp,z+zp) for xp in range(-dist,dist+1) for yp in range(-dist,dist+1) for zp in range(-dist,dist+1) if ( (xp**2+yp**2+zp**2) <= dist**2+sys.float_info.epsilon ) )
答案 1 :(得分:3)
首先,我建议您使用for
替换三重嵌套的itertools.product()
循环,如下所示:
import itertools as it
it_1 = it.product(range(-dist, dist+1), repeat=3)
如果您使用的是Python 2.x,则应在此使用xrange()
而不是range()
。
接下来,不是使用filter()
,而是使用生成器表达式:
it_2=(x, y, z for x, y, z in it_1 if ((x-p[0])**2+(y-p[1])**2+(z-p[2])**2) <= dist**2+sys.float_info.epsilon)
这将避免Python 2.x中的一些开销(因为filter()
构建一个列表),但是对于Python 3.x将大致相同;甚至在Python 2.x中你可以使用itertools.ifilter()
。
但为了便于阅读,我会将整个内容打包成一个生成器,如下所示:
import itertools as it
import sys
def sphere_points(radius=0, origin=(0,0,0), epsilon=sys.float_info.epsilon):
x0, y0, z0 = origin
limit = radius**2 + epsilon
for x, y, z in it.product(range(-radius, radius+1), repeat=3):
if (x**2 + y**2 + z**2) <= limit:
yield (x+x0, y+y0, z+z0)
我刚刚更改了原始代码中的代码。 x,y和z的每个范围都调整为以原点为中心。当我测试半径为0的代码时,我正确地返回一个点,原点。
请注意,我为函数提供了参数,让您指定半径,原点,甚至是用于epsilon的值,每个都有默认值。我还将原点元组解压缩为显式变量;我不确定Python是否会优化索引操作,但这样我们就知道循环内不会有任何索引。 (我认为Python编译器可能会将limit
计算提升出循环,但为了便于阅读,我实际上更喜欢它自己的行,如图所示。)
我认为上述内容与您在本机Python中编写内容的速度一样快,我认为这是可读性的重大改进。
P.S。如果使用Cython重做代码,这段代码可能运行得更快。
编辑:代码简化了@eryksun在评论中的建议。
答案 2 :(得分:2)
您对epsilon
的使用有点偏差。 sys.float_info的文档将它描述为1和下一个可表示的float之间的差异,因此它太小而不能在2上产生差异,更不用说2 ** 2了。
其次,所有点都是从滤波器中的x,y,z开始测量的,然后在结果表达式(x+xp,y+yp,z+zp)
中由它进行偏移。你会以这种方式获得偏离中心的球体,所以试试(xp,yp,zp)
。
答案 3 :(得分:0)
其他人指出了逻辑错误。我会解决可读性问题。另请注意,给出的数据不涉及浮点,因此不需要epsilon。
from itertools import product
from pprint import pprint
x,y,z = 0,0,1
r = 2
def points_in_circle(radius):
'''return a generator of all integral points in circle of given radius.'''
return ((x,y,z)
for x,y,z in product(range(-dist,dist+1),repeat=3)
if x**2 + y**2 + z**2 <= radius**2)
# List integral points of radius r around point (x,y,z).
pprint([(x+xp,y+yp,z+zp) for xp,yp,zp in points_in_circle(r)])
[(-2, 0, 1),
(-1, -1, 0),
(-1, -1, 1),
(-1, -1, 2),
(-1, 0, 0),
(-1, 0, 1),
(-1, 0, 2),
(-1, 1, 0),
(-1, 1, 1),
(-1, 1, 2),
(0, -2, 1),
(0, -1, 0),
(0, -1, 1),
(0, -1, 2),
(0, 0, -1),
(0, 0, 0),
(0, 0, 1),
(0, 0, 2),
(0, 0, 3),
(0, 1, 0),
(0, 1, 1),
(0, 1, 2),
(0, 2, 1),
(1, -1, 0),
(1, -1, 1),
(1, -1, 2),
(1, 0, 0),
(1, 0, 1),
(1, 0, 2),
(1, 1, 0),
(1, 1, 1),
(1, 1, 2),
(2, 0, 1)]