使用Python元组作为向量

6

我需要在 Python 中表示不可变向量("向量" 是指线性代数中的向量,而不是编程中的向量)。元组似乎是一个很明显的选择。

问题出现在我需要实现诸如加法和标量乘法之类的操作时。如果ab是向量,c是一个数字,则我能想到的最好方法是:

tuple(map(lambda x,y: x + y, a, b)) # add vectors 'a' and 'b'
tuple(map(lambda x: x * c, a))      # multiply vector 'a' by scalar 'c'

这种方式似乎不够优雅,应该有更清晰、更简单的方法来完成此操作 - 更不用说避免调用tuple,因为map返回一个列表。

是否有更好的选择?


2
你可以用itertools.imap替换map函数,这样可以避免创建临时列表的开销,更不用说避免调用元组了,因为imap返回一个迭代器。我相信在Python 3.x中,map函数总是会返回一个迭代器。 - Dave Kirby
7个回答

11

Python中和第三方扩展中几乎没有不可变类型;提问者正确地指出“线性代数有足够多的用途,看起来我不得不自己编写”,但是我所知道的所有执行线性代数的现有类型都是可变的!因此,由于提问者坚持使用不可变类型,他只能自己编写。

并不是说需要做很多工作,例如,如果你确切地需要2-d向量:

import math
class ImmutableVector(object):
    __slots__ = ('_d',)
    def __init__(self, x, y):
        object.__setattr__(self, _d, (x, y))
    def __setattr__(self, n, v):
        raise ValueError("Can't alter instance of %s" % type(self))
    @property
    def x(self): 
        return self._d[0]
    @property
    def y(self):
        return self._d[1]
    def __eq__(self, other):
        return self._d == other._d
    def __ne__(self, other):
        return self._d != other._d
    def __hash__(self):
        return hash(self._d)
    def __add__(self, other):
        return type(self)(self.x+other.x, self.y+other.y)
    def __mul__(self, scalar):
        return type(self)(self.x*scalar, self.y*scalar)
    def __repr__(self):
        return '%s(%s, %s)' % (type(self).__name__, self.x, self.y)
    def __abs__(self):
        return math.hypot(self.x, self.y)

我“免费添加”了一些额外的功能,例如.x.y只读属性、良好的字符串表示、可用于集合或作为字典键(否则为什么要不可变性?),低内存占用,abs(v)返回v的向量长度--我相信您可以想到其他“如果有就太酷了”的方法和运算符,取决于您的应用领域,它们同样容易。如果您需要其他维度,这不会更难,尽管稍微不那么易读,因为.x.y表示法不再适用;-)(但我会使用生成器表达式而不是map)。


+1 继续努力!我确实想要一个n维解决方案(看起来我可能会选择NumPy并重新思考可变性),但这值得考虑一下。 - Etaoin

11

NumPy 支持使用其数组进行各种代数运算。


+1 for numpy。Numpy的数组可能已经满足了你95%的需求。试图重新实现众所周知和长期使用的库是没有意义的。 - user297250

9

虽然使用像NumPy这样的库似乎是解决问题的方法,但我认为一个简单的、不需要额外库,并且你可以用可迭代对象保持不变的解决方案仍然有一定的价值。

使用itertoolsoperators模块:

imap(add, a, b) # returns iterable to sum of a and b vectors

这个实现很简单。它没有使用lambda,也没有使用任何列表-元组转换,因为它是基于迭代器的。

from itertools import imap
from operator import add
vec1 = (1, 2, 3)
vec2 = (10, 20, 30)
result = imap(add, vec1, vec2)
print(tuple(result))

产出:

(11, 22, 33)

1
我正在使用Python3在RaspberryPi Zero上工作,但是我遇到了ImportError: cannot import name 'imap'的错误。 - Ryuuzaki Julio
我修复了答案的代码:对于Python 3,请使用map而不是imap。 - Nicola

8
通过从元组(tuple)继承,您可以轻松地创建一个漂亮的向量(Vector)类。以下是足够的代码,提供向量的加法和向量与标量的乘法。它可以给出任意长度的向量,并且可以处理复数、整数或浮点数。
class Vector(tuple):
    def __add__(self, a):
        # TODO: check lengths are compatable.
        return Vector(x + y for x, y in zip(self, a))
    def __mul__(self, c):
        return Vector(x * c for x in self)
    def __rmul__(self, c):
        return Vector(c * x for x in self)

a = Vector((1, 2, 3))
b = Vector((2, 3, 4))

print a + b
print 3 * a
print a * 3

3
为什么不创建自己的类,利用两个笛卡尔点成员变量呢?(如果语法有点问题,抱歉,我的Python有点生疏)
class point:
    def __init__(self,x,y):
        self.x=x
        self.y=y
        #etc

    def add(self,p):
        return point(self.x + p.x, self.y + p.y)

class vector:
    def __init__(self,a,b):
        self.pointA=a
        self.pointB=b
        #etc

    def add(self,v):
        return vector(self.pointA + v.pointA, self.pointB + v.pointB)

主要是为了避免重复造轮子;线性代数有足够多的用途,看起来我不需要自己编写。 - Etaoin
@Etaoin 如果你想使用自己的元组而不是使用别人创建的向量类,那么你已经在重新发明轮子了。:D - Gordon Gustafson
1
希望你不介意,我为你修正了语法(为了日后的读者受益)。 - David Z

2

对于偶尔使用的情况,可以使用标准运算符包来实现Python 3解决方案,而无需重复使用lambda:

from operator import add, mul

a = (1, 2, 3)
b = (4, 5, 6)

print(tuple(map(add, a , b)))
print(tuple(map(mul, a , b)))

这打印出以下内容:

(5, 7, 9)
(4, 10, 18)

对于需要进行严格的线性代数计算的情况,使用numpy向量是最常用的解决方案:

import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

print(a+b)
print(a*b)

这将打印:

[5 7 9]
[ 4 10 18]

0

由于几乎所有的序列操作函数都返回列表,因此这就是你必须要做的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接