簡體   English   中英

從Python中的元組列表中獲取每個元組的第n個元素的最佳方法

[英]Best way to get the nth element of each tuple from a list of tuples in Python

我有一些代碼包含zip(*G)[0] (和其他地方, zip(*G)[1] ,有不同的G)。 G是元組列表。 這樣做是返回zip(*G)[n]中每個元組的第一個(或者一般來說,對於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創建的所有元組。 我只需要G中每個元組的第n個元素。

是否有更高效,但同樣緊湊的方式來做到這一點? 我對標准庫中的任何東西都很滿意。 在我的用例中,G中的每個元組將至少長度為n ,因此無需擔心zip停在最小長度元組的情況(即, zip(*G)[n]將始終定義) 。

如果沒有,我想我會堅持將zip包裝在list()

(PS,我知道這是不必要的優化。我只是好奇就是全部)

更新:

如果有人關心,我選擇了t0, t1, t2 = zip(*G)選項。 首先,這可以讓我為數據提供有意義的名稱。 我的G實際上由長度為2的元組組成(代表分子和分母)。 列表理解只比zip更容易閱讀,但這種方式要好得多(因為在大多數情況下,zip是列表我在列表理解中迭代,這使事情變得更平坦)。

其次,正如@thewolf和@Sven Marnach的優秀答案所指出的,對於較小的列表,這種方式更快。 在大多數情況下,我的G實際上並不大(如果它很大,那么這肯定不會是代碼的瓶頸!)。

但是有更多的方法可以做到這一點,包括我甚至不知道的Python 3的新的a, *b, c = G特性。

您可以使用列表理解

[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. 狼的回答相比,我從f4()移除了對tuple()的冗余調用tuple()表達式的結果已經是元組),並且我添加了一個函數f7() ,它只能提取第一列。

正如預期的那樣,列表推導速度最快,而且一般不太通用的f7()

另一個編輯 :以下是十列而不是三列的結果,代碼在適當的地方進行了調整:

f1 0.7429649829864502
f2 0.881648063659668
f3 1.234360933303833
f4 1.92038893699646
f5 1.9218590259552002
f6 1.9172680377960205
f7 0.6230220794677734

至少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交易場所最快返回元組。

一個非常聰明的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')

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM