列表推导中用于同时过滤和转换的中间变量
我有一个向量列表(在Python中),我想对它们进行归一化,同时去掉那些原本模长很小的向量。
输入列表是,比如说:
a = [(1,1),(1,2),(2,2),(3,4)]
我需要的输出是 (x*n, y*n)
,其中 n = (x**2+y**2)**-0.5
如果我只需要模长,那用列表推导式就很简单:
an = [ (x**2+y**2)**0.5 for x,y in a ]
如果我只想存储归一化后的x值也很简单,但我想要一个临时变量“n”,用在两个计算中,然后再丢掉它。
我也不能仅仅用一个lambda函数,因为我还需要用n来过滤列表。那么,最好的方法是什么呢?
现在我在这里使用这个嵌套的列表推导式(内部列表中有一个表达式):
a = [(1,1),(1,2),(2,2),(3,4)]
[(x*n,y*n) for (n,x,y) in (( (x**2.+y**2.)**-0.5 ,x,y) for x,y in a) if n < 0.4]
# Out[14]:
# [(0.70710678118654757, 0.70710678118654757),
# (0.60000000000000009, 0.80000000000000004)]
内部列表生成带有额外值(n)的元组,然后我用这些值进行计算和过滤。这真的是最好的方法吗?有没有什么效率低下的地方我需要注意的?
4 个回答
1
这段话的意思是,使用for循环可能是最快的方法。不过,最好在你自己的电脑上检查一下timeit的结果,因为这些结果会受到很多因素的影响,比如硬件、操作系统、Python版本、以及的长度等等。
a = [(1,1),(1,2),(2,2),(3,4)]
def two_lcs(a):
an = [ ((x**2+y**2)**0.5, x,y) for x,y in a ]
an = [ (x*n,y*n) for n,x,y in an if n < 0.4 ]
return an
def using_forloop(a):
result=[]
for x,y in a:
n=(x**2+y**2)**0.5
if n<0.4:
result.append((x*n,y*n))
return result
def using_lc(a):
return [(x*n,y*n)
for (n,x,y) in (( (x**2.+y**2.)**-0.5 ,x,y) for x,y in a) if n < 0.4]
会得到这些timeit的结果:
% python -mtimeit -s'import test' 'test.using_forloop(test.a)'
100000 loops, best of 3: 3.29 usec per loop
% python -mtimeit -s'import test' 'test.two_lcs(test.a)'
100000 loops, best of 3: 4.52 usec per loop
% python -mtimeit -s'import test' 'test.using_lc(test.a)'
100000 loops, best of 3: 6.97 usec per loop
4
从 Python 3.8
开始,引入了 赋值表达式 (PEP 572),也就是 :=
这个符号。这样一来,我们就可以在列表推导式中使用局部变量,这样就能避免多次计算同一个表达式。
在我们的例子中,我们可以把 (x**2.+y**2.)**-.5
的计算结果命名为一个变量 n
,然后用这个结果来过滤列表,前提是 n
小于 0.4
;接着,我们还可以用 n
来生成映射值:
# vectors = [(1, 1), (1, 2), (2, 2), (3, 4)]
[(x*n, y*n) for x, y in vectors if (n := (x**2.+y**2.)**-.5) < .4]
# [(0.7071067811865476, 0.7071067811865476), (0.6000000000000001, 0.8)]
11
Is this really the best way?
嗯,这个方法确实很有效,如果你真的很想写一行代码的话,那这个是你能做到的最好选择。
不过,简单的四行函数也能做到同样的事情,而且看起来更清晰:
def normfilter(vecs, min_norm):
for x,y in vecs:
n = (x**2.+y**2.)**-0.5
if min_norm < n:
yield (x*n,y*n)
normalized = list(normfilter(vectors, 0.4))
顺便说一下,你的代码或者描述里有个错误——你说你在过滤掉短向量,但你的代码正好相反 :p