12

我有一些代码包含zip(*G)[0](和其他地方,zip(*G)[1]有不同的 G)。 G是一个元组列表。这样做是将G 中每个元组的第一个(或通常,对于 th)元素的列表作为元组返回zip(*G)[n]n-1例如,

>>> G = [(1, 2, 3), ('a', 'b', 'c'), ('you', 'and', 'me')]
>>> zip(*G)[0]
(1, 'a', 'you')
>>> zip(*G)[1]
(2, 'b', 'and')

这非常聪明,但问题是它在 Python 3 中不起作用,因为zip那里有一个迭代器。此外,2to3 还不够聪明,无法修复它。所以显而易见的解决方案是使用list(zip(*G))[0],但这让我想到:可能有一种更有效的方法来做到这一点。不需要创建 zip 创建的所有元组。我只需要nG 中每个元组的第 th 个元素。

有没有更有效但同样紧凑的方法来做到这一点?我对标准库中的任何内容都满意。在我的用例中,G 中的每个元组至少是 length n,因此无需担心 zip 会在最小长度的元组处停止(即,zip(*G)[n]将始终定义)。

如果没有,我想我会坚持将 in 包装zip起来list()

(PS,我知道这是不必要的优化。我只是好奇而已)

更新:

万一有人在乎,我选择了这个t0, t1, t2 = zip(*G)选项。首先,这让我可以给数据起有意义的名字。MyG实际上由长度为 2 的元组组成(代表分子和分母)。列表理解只会比 zip 更具可读性,但这种方式要好得多(因为在大多数情况下,zip 是我在列表理解中迭代的列表,这让事情变得更平坦)。

其次,正如@thewolf 和@Sven Marnach 的出色答案所指出的那样,这种方式对于较小的列表来说更快。在大多数情况下,我的 G 实际上并不大(如果它很大,那么这绝对不会成为代码的瓶颈!)。

但是有比我预期更多的方法来做到这一点,包括a, *b, c = G我什至不知道的 Python 3 的新特性。

4

3 回答 3

18

您可以使用列表推导

[x[0] for x in G]

或者operator.itemgetter()

from operator import itemgetter
map(itemgetter(0), G)

或序列解包

[x for x, y, z in G]

编辑:这是我对不同选项的时间安排,也在 Python 3.2 中:

from operator import itemgetter
import timeit

G = list(zip(*[iter(range(30000))] * 3))

def f1():
    return [x[0] for x in G]
def f2():
    return list(map(itemgetter(0), G))
def f3():
    return [x for x, y, z in G]
def f4():
    return list(zip(*G))[0]
def f5():
    c0, *rest = zip(*G)
    return c0
def f6():
    c0, c1, c2 = zip(*G)
    return c0
def f7():
    return next(zip(*G))

for f in f1, f2, f3, f4, f5, f6, f7:
    print(f.__name__, timeit.timeit(f, number=1000))

我的机器上的结果:

f1 0.6753780841827393
f2 0.8274149894714355
f3 0.5576457977294922
f4 0.7980241775512695
f5 0.7952430248260498
f6 0.7965989112854004
f7 0.5748469829559326

评论:

  1. 我使用了一个包含 10000 个三元组的列表来测量实际处理时间,并使函数调用开销、名称查找等可以忽略不计,否则会严重影响结果。

  2. 这些函数返回一个列表或一个元组——任何对特定解决方案更方便的方法。

  3. 狼的回答tuple()相比,我删除了对from的冗余调用f4()(表达式的结果已经是一个元组),并且我添加了一个f7()仅用于提取第一列的函数。

正如预期的那样,列表推导是最快的,还有一些不太通用的f7()

另一个编辑:这是十列而不是三列的结果,并在适当的地方调整了代码:

f1 0.7429649829864502
f2 0.881648063659668
f3 1.234360933303833
f4 1.92038893699646
f5 1.9218590259552002
f6 1.9172680377960205
f7 0.6230220794677734
于 2012-09-15T19:27:17.463 回答
14

至少Python 2.7 中最快的方法是

t0,t1,t2=zip(*G) for SMALLER lists and [x[0] for x in G] in general

这是测试:

from operator import itemgetter

G = [(1, 2, 3), ('a', 'b', 'c'), ('you', 'and', 'me')]

def f1():
   return tuple(x[0] for x in G)

def f2():
   return tuple(map(itemgetter(0), G))

def f3():
    return tuple(x for x, y, z in G)     

def f4():
    return tuple(list(zip(*G))[0])

def f5():
    t0,*the_rest=zip(*G)
    return t0

def f6():
    t0,t1,t2=zip(*G)
    return t0                

cmpthese.cmpthese([f1,f2,f3,f4,f5,f6],c=100000) 

结果:

    rate/sec     f4     f5     f1     f2     f3     f6
f4   494,220     -- -21.9% -24.1% -24.3% -26.6% -67.6%
f5   632,623  28.0%     --  -2.9%  -3.0%  -6.0% -58.6%
f1   651,190  31.8%   2.9%     --  -0.2%  -3.2% -57.3%
f2   652,457  32.0%   3.1%   0.2%     --  -3.0% -57.3%
f3   672,907  36.2%   6.4%   3.3%   3.1%     -- -55.9%
f6 1,526,645 208.9% 141.3% 134.4% 134.0% 126.9%     --

如果您不在乎结果是否是列表,那么列表理解会更快。

这是一个具有可变列表大小的更扩展的基准测试:

from operator import itemgetter
import time
import timeit 
import matplotlib.pyplot as plt

def f1():
   return [x[0] for x in G]

def f1t():
   return tuple([x[0] for x in G])

def f2():
   return tuple([x for x in map(itemgetter(0), G)])

def f3():
    return tuple([x for x, y, z in G])    

def f4():
    return tuple(list(zip(*G))[0])

def f6():
    t0,t1,t2=zip(*G)
    return t0     

n=100    
r=(5,35)
results={f1:[],f1t:[],f2:[],f3:[],f4:[],f6:[]}    
for c in range(*r):
    G=[range(3) for i in range(c)] 
    for f in results.keys():
        t=timeit.timeit(f,number=n)
        results[f].append(float(n)/t)

for f,res in sorted(results.items(),key=itemgetter(1),reverse=True):
    if f.__name__ in ['f6','f1','f1t']:
        plt.plot(res, label=f.__name__,linewidth=2.5)
    else:    
        plt.plot(res, label=f.__name__,linewidth=.5)

plt.ylabel('rate/sec')
plt.xlabel('data size => {}'.format(r))  
plt.legend(loc='upper right')
plt.show()

这会为较小的数据大小(5 到 35)生成此图:

更小

此输出适用于更大范围(25 到 250):

较大

你可以看到f1,列表理解是最快的。f6f1t以最快的速度返回元组。

于 2012-09-15T21:45:06.190 回答
6

一个非常聪明的Python 3 唯一方法是使用星号分配或扩展可迭代解包

>>> G = [(1, 2, 3), ('a', 'b', 'c'), ('you', 'and', 'me')]
>>> items_I_want,*the_rest=zip(*G)
>>> items_I_want
(1, 'a', 'you')
>>> the_rest
[(2, 'b', 'and'), (3, 'c', 'me')]

由于您正在为两者编写代码,因此您可以使用显式解包(适用于 Python 2 和 Python 3):

>>> z1,z2,z3=zip(*G)
>>> z1
(1, 'a', 'you')
>>> z2
(2, 'b', 'and')
>>> z3
(3, 'c', 'me')
于 2012-09-15T20:56:41.653 回答