Лучший способ получить n-й элемент каждого кортежа из списка кортежей в Python

У меня был код, содержащий zip(*G)[0] (и в другом месте zip(*G)[1], с другим G). G - список кортежей. То, что это делает, возвращает список первого (или вообще, для zip(*G)[n], n-1 th) элемента каждого кортежа в G как кортеж. Например,

>>> 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. Мне просто нужен n -й элемент каждого набора в G.

Есть ли более эффективный, но одинаково компактный способ сделать это? У меня все в порядке со стандартной библиотекой. В моем случае использования каждый кортеж в G будет иметь длину не менее n, поэтому нет необходимости беспокоиться о случае остановки zip на кортежей с наименьшей длиной (т.е. Всегда будет определен zip(*G)[n]).

Если нет, я думаю, что я просто придерживаюсь упаковки zip в list().

(P.S., я знаю, что это ненужная оптимизация. Мне просто интересно)

UPDATE:

Если кто-то заботится, я пошел с опцией t0, t1, t2 = zip(*G). Во-первых, это позволяет мне давать значимые имена данным. Мой G фактически состоит из 2 кортежей (представляющих числители и знаменатели). Понимание списка было бы чуть более читаемым, чем zip, но этот способ намного лучше (и поскольку в большинстве случаев zip был списком, который я повторял в понимании списка, это делает вещи более плоскими). ​​

Во-вторых, как замечают @thewolf и @Sven Marnach отличные ответы, этот способ быстрее для небольших списков. В большинстве случаев мой G на самом деле невелик (и если он большой, то это определенно не будет узким местом кода!).

Но было больше способов сделать это, чем я ожидал, включая новую функцию a, *b, c = G для Python 3, о которой я даже не знал.

Ответ 1

По крайней мере, самый быстрый способ в 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):

smaller

И этот выход для больших диапазонов (от 25 до 250):

larger

Вы можете видеть, что f1, понимание списка выполняется быстрее всего. f6 и f1t торговые места как самые быстрые, чтобы вернуть кортеж.

Ответ 2

Вы можете использовать понимание списка

[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

Комментарии:

  • Я использовал список с 10000 тройками, чтобы измерить фактическое время обработки, а также накладные расходы на функционирование, поиск имен и т.д. пренебрежимо мало, что в противном случае серьезно повлияло бы на результаты.

  • Функции возвращают список или кортеж - что более удобно для конкретного решения.

  • По сравнению с ответом волка, я удалил избыточный вызов tuple() из f4() (результат выражения уже является кортежем), и я добавил функцию f7(), которая работает только для извлечения первого столбца.

Как и ожидалось, наиболее вероятными являются списки, а также несколько менее общие f7().

Другое редактирование. Ниже приведены результаты для десяти столбцов вместо трех, с соответствующим кодом, соответствующим:

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

Ответ 3

Очень умный Только для 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')