6

想象一个离散的 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)

它收集所有点,然后过滤那些不符合条件的点。

我希望有一种方法可以纠正第一次尝试的实现,或者使这些表达式更具可读性或更紧凑。

4

4 回答 4

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 ) )
于 2013-03-24T20:23:54.840 回答
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 写的一样快,而且我认为它在可读性上是一个很大的改进。

PS 如果使用 Cython 重做这段代码,它可能会运行得更快。

http://cython.org/

编辑:按照@eryksun 在评论中的建议简化代码。

于 2013-03-24T21:51:36.680 回答
2

你的使用epsilon有点过。sys.float_info 的文档将其描述为 1 和下一个可表示的浮点数之间的差异,因此它太小而无法在 2 上产生差异,更不用说 2**2。

其次,您的所有点都是从过滤器中的 x,y,z 测量的/如果然后在结果表达式中被它偏移(x+xp,y+yp,z+zp)。这样你会得到一个相当偏离中心的球体,所以试试(xp,yp,zp).

于 2013-03-24T20:26:06.233 回答
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)]
于 2013-03-25T02:20:14.737 回答