10

在 Python 2.7+ 和 3.x 中都可以使用的高效 Vector / Point 类(甚至更好:已经有一个)的最佳方法是什么?

我找到了 blender-mathutils,但它们似乎只支持 Python 3.x。然后是这个 Vector 类,它使用numpy,但它只是一个 3D 向量。使用具有静态属性(x 和 y)的向量(如kivy 的向量类源代码))的列表似乎也很奇怪。(有所有这些列表方法。)

目前我正在使用扩展 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.arrayornumpy.ndarray作为向量太慢了。(例如,获取一个项目几乎需要两倍的时间,更不用说创建一个数组。我认为它更适合对大量项目进行计算。)

所以,我正在寻找一个轻量级的矢量类,它具有固定数量的字段(在我的例子中只是xy),可用于游戏。(如果已经有一个经过充分测试的轮子,我不想重新发明轮子。)

4

3 回答 3

16

是的,有一个向量类:它在事实上的标准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()) 等。

于 2013-10-18T20:16:40.457 回答
5

就线性代数而言,向量numpy可能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

在最简单的情况下,您可以通过将 an 查看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

这是有关子类化的ndarray更多信息。

于 2013-10-18T21:21:13.937 回答
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()
于 2015-10-14T03:46:53.780 回答