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が正しい結果を生成しないことを確認します。私はそれが3番目の(すなわち: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()リストを作成するため)が、Python3.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でテストすると、原点である単一の点が正しく返されます。

関数に引数を指定して、半径、原点、さらにはイプシロンに使用する値を指定できることに注意してください。それぞれにデフォルトがあります。また、原点タプルを明示的な変数に解凍しました。Pythonがインデックス作成操作を最適化するかどうかはわかりませんが、このようにして、ループ内でインデックス作成が行われないことがわかります。(Pythonコンパイラーはおそらく計算をループから引き上げると思いますがlimit、読みやすくするために、実際にはここに示すように独自の行で計算することをお勧めします。)

上記はネイティブPythonで記述できる速度とほぼ同じだと思います。また、読みやすさが大幅に向上したと思います。

PSこのコードは、Cythonを使用してやり直した場合、おそらくはるかに高速に実行されます。

http://cython.org/

編集:コメントで@eryksunによって提案されたようにコードが簡略化されました。

于 2013-03-24T21:51:36.680 に答える
2

の使用はepsilon少しずれています。sys.float_infoのドキュメントでは、1と次の表現可能なfloatの違いとして説明されているため、2 ** 2は言うまでもなく、2で違いを生むには小さすぎます。

次に、すべてのポイントがフィルターのx、y、zから測定されます。その後、結果式でそれによってオフセットされます(x+xp,y+yp,z+zp)。そうすれば、中心から外れた球体が得られるので、試してみてください(xp,yp,zp)

于 2013-03-24T20:26:06.233 に答える
0

他の人は論理エラーを指摘しました。読みやすさについて説明します。また、与えられたデータでは、浮動小数点が含まれていないため、イプシロンは必要ありません。

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 に答える