实现高效的Vector / Point类的最佳方法是什么(甚至更好:是否已有),可以在Python 2.7+和3.x中使用?
我找到了the blender-mathutils,但它们似乎只支持Python 3.x.然后是使用this Vector class的numpy,但它只是一个3D矢量。使用具有静态属性(x和y)的向量kivy's vector class(sourcecode)的列表似乎也很奇怪。 (所有这些列表方法都有。)
目前我正在使用扩展了namedtuple的类(如下所示),但这样做的缺点是无法更改坐标。我认为当数千个对象移动并且每次都创建一个新的(矢量)元组时,这可能会成为一个性能问题。 (右?)
class Vector2D(namedtuple('Vector2D', ('x', 'y'))):
__slots__ = ()
def __abs__(self):
return type(self)(abs(self.x), abs(self.y))
def __int__(self):
return type(self)(int(self.x), int(self.y))
def __add__(self, other):
return type(self)(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return type(self)(self.x - other.x, self.y - other.y)
def __mul__(self, other):
return type(self)(self.x * other, self.y * other)
def __div__(self, other):
return type(self)(self.x / other, self.y / other)
def dot_product(self, other):
return self.x * other.x + self.y * other.y
def distance_to(self, other):
""" uses the Euclidean norm to calculate the distance """
return hypot((self.x - other.x), (self.y - other.y))
编辑:我做了一些测试,似乎使用numpy.array
或numpy.ndarray
作为向量太慢了。 (例如,获取一个项目需要几乎两倍的时间,更不用说创建一个数组了。我认为它对于对大量项目进行计算更加优化。)
所以,我正在寻找一个可以用于游戏的具有固定数量字段(在我的情况下只有x
和y
)的轻量级矢量类。 (如果已经有一个经过充分测试的车轮,我不想重新发明轮子。)
答案 0 :(得分:15)
是的,有一个矢量类:它位于事实上的标准NumPy模块中。你可以像这样创建矢量:
>>> v = numpy.array([1, 10, 123])
>>> 2*v
array([ 2, 20, 246])
>>> u = numpy.array([1, 1, 1])
>>> v-u
array([ 0, 9, 122])
NumPy非常丰富,可让您访问快速数组操作:点积(numpy.dot()
),范数(numpy.linalg.norm()
)等。
答案 1 :(得分:3)
线性代数中numpy
中的 vector 类可能是numpy.matrix
,它是numpy.ndarray
的子类。它不是更清晰本身,但它使代码更清晰,因为假设代数运算而不是元素。
In [77]: a = np.array([1,2])
In [78]: b = np.array([3,3])
In [79]: a*b
Out[79]: array([3, 6])
In [80]: np.dot(a,b)
Out[80]: 9
In [81]: np.outer(a,b)
Out[81]:
array([[3, 3],
[6, 6]])
In [82]: a = np.matrix(a).T
In [83]: b = np.matrix(b)
In [84]: b*a
Out[84]: matrix([[9]])
In [85]: a*b
Out[85]:
matrix([[3, 3],
[6, 6]])
如果您想创建自己的,请将其基于其中一个,例如:
class v2d(np.ndarray):
def __abs__(self):
return np.linalg.norm(self)
def dist(self,other):
return np.linalg.norm(self-other)
def dot(self, other):
return np.dot(self, other)
# and so on
在最简单的情况下,您可以通过查看ndarray
作为新班级来制作:
In [63]: a = np.array([1,2]).view(v2d)
In [64]: b = np.array([3,3]).view(v2d)
In [65]: a
Out[65]: v2d([1, 2])
In [66]: abs(b)
Out[66]: 4.2426406871192848
In [67]: a - b
Out[67]: v2d([-2, -1])
In [68]: a*b
Out[68]: v2d([3, 6])
In [69]: a*3
Out[69]: v2d([3, 6])
In [70]: a.dist(b)
Out[70]: 2.2360679774997898
In [71]: b.dist(a)
Out[71]: 2.2360679774997898
In [72]: a.dot(b)
Out[72]: 9
以下是有关subclassing the ndarray
的更多信息。
答案 2 :(得分:0)
我也需要一个快速的解决方案,所以我只是将numpy的数组包装到我自己的数组中。您会注意到一些可以根据自己的需求进行更改的设计决策(如默认设置)。 如果您想使用它:https://gist.github.com/eigencoder/c029d7557e1f0828aec5
import numpy as np
class Point(np.ndarray):
"""
n-dimensional point used for locations.
inherits +, -, * (as dot-product)
> p1 = Point([1, 2])
> p2 = Point([4, 5])
> p1 + p2
Point([5, 7])
See ``test()`` for more usage.
"""
def __new__(cls, input_array=(0, 0)):
"""
:param cls:
:param input_array: Defaults to 2d origin
"""
obj = np.asarray(input_array).view(cls)
return obj
@property
def x(self):
return self[0]
@property
def y(self):
return self[1]
@property
def z(self):
"""
:return: 3rd dimension element. 0 if not defined
"""
try:
return self[2]
except IndexError:
return 0
def __eq__(self, other):
return np.array_equal(self, other)
def __ne__(self, other):
return not np.array_equal(self, other)
def __iter__(self):
for x in np.nditer(self):
yield x.item()
def dist(self, other):
"""
Both points must have the same dimensions
:return: Euclidean distance
"""
return np.linalg.norm(self - other)
def test():
v1 = Point([1, 2, 3])
v2 = Point([4, 5, 7])
v3 = Point([4, ])
sum12 = Point([5, 7, 10])
dot12 = Point([4, 10, 21])
# Access
assert v2.x == 4
assert v2.y == 5
assert v2.z == 7
assert v3.z == 0
assert Point().x == 0
assert v2[0] == 4
assert v1[-1] == 3 # Not needed but inherited
assert [x for x in v2] == [4, 5, 7], "Iteration should return all elements"
# Operations
assert v1 + v2 == sum12
assert v1 * v2 == dot12
assert v1.dist(v2) ** 2 == 34
assert v1 != v2
assert v2.size == 3, "v2 should be a 3d point"
print "pass"
if __name__ == "__main__":
test()