Быстрая итерация по первым n элементам итерируемого (не списка) в Python - PullRequest
12 голосов
/ 24 апреля 2010

Я ищу питонический способ итерации по первым n элементам итерируемого ( upd : не список в общем случае, поскольку для списков все тривиально), и это довольно важно сделать это как можно быстрее. Вот как я это делаю сейчас:

count = 0
for item in iterable:
 do_something(item)
 count += 1
 if count >= n: break

Мне не кажется опрятным. Еще один способ сделать это:

for item in itertools.islice(iterable, n):
    do_something(item)

Это выглядит хорошо, вопрос в том, достаточно ли это быстро для использования с некоторыми генераторами? Например:

pair_generator = lambda iterable: itertools.izip(*[iter(iterable)]*2)
for item in itertools.islice(pair_generator(iterable), n):
 so_something(item)

Будет ли он работать достаточно быстро по сравнению с первым методом? Есть ли какой-нибудь более простой способ сделать это?

Ответы [ 5 ]

15 голосов
/ 24 апреля 2010

for item in itertools.islice(iterable, n): - самый очевидный и простой способ сделать это. Он работает для произвольных итераций и равен O (n), как и любое разумное решение.

Возможно, что другое решение может иметь лучшую производительность; мы бы не узнали без времени. Я не рекомендовал бы беспокоиться о времени, если вы не профиль свой код и не найдете этот вызов горячей точки. Если это не похоронено во внутренней петле, очень сомнительно, что это будет. Преждевременная оптимизация - корень всего зла.


Если бы я собирался искать альтернативные решения, я бы посмотрел на такие, как for count, item in enumerate(iterable): if count > n: break ... и for i in xrange(n): item = next(iterator) .... Не думаю, что это поможет, но, похоже, стоит попробовать, если мы действительно хотим сравнить вещи. Если бы я застрял в ситуации, когда я профилировал и обнаружил, что это была горячая точка во внутреннем цикле (это действительно ваша ситуация?), Я бы также попытался облегчить поиск по имени от получения islice атрибут глобального iterools для привязки функции уже к локальному имени.

Это вещи, которые вы делаете только после того, как доказали, что они помогут. Люди стараются делать их часто. Это не помогает сделать их программы заметно быстрее; это только ухудшает их программы.

6 голосов
/ 24 апреля 2010

itertools имеет тенденцию быть самым быстрым решением, когда оно непосредственно применимо.

Очевидно, что единственный способ проверить - это выполнить эталонный тест - например, сохранить в aaa.py

import itertools

def doit1(iterable, n, do_something=lambda x: None):
  count = 0
  for item in iterable:
   do_something(item)
   count += 1
   if count >= n: break

def doit2(iterable, n, do_something=lambda x: None):
  for item in itertools.islice(iterable, n):
      do_something(item)

pair_generator = lambda iterable: itertools.izip(*[iter(iterable)]*2)

def dd1(itrbl=range(44)): doit1(itrbl, 23)
def dd2(itrbl=range(44)): doit2(itrbl, 23)

и посмотри ...:

$ python -mtimeit -s'import aaa' 'aaa.dd1()'
100000 loops, best of 3: 8.82 usec per loop
$ python -mtimeit -s'import aaa' 'aaa.dd2()'
100000 loops, best of 3: 6.33 usec per loop

так ясно, что itertools быстрее здесь - сравните с вашими данными для проверки.

Кстати, я нахожу timeit НАМНОГО более пригодным для использования из командной строки, так что я всегда использую его - тогда он запускает правильный "порядок величины" циклов для скоростей, которые вы специально пытаетесь мера, будь то 10, 100, 1000 и т. д. - здесь, чтобы различить микросекунды и половину разницы, сто тысяч петель примерно правильные.

2 голосов
/ 24 апреля 2010

Вы можете использовать перечисление , чтобы написать, по сути, тот же цикл, что и у вас, но более простым, Pythonic способом:

for idx, val in enumerate(iterableobj):
    if idx > n:
        break
    do_something(val)
2 голосов
/ 24 апреля 2010

Если это список, то вы можете использовать нарезку:

list[:n]
1 голос
/ 24 апреля 2010

из списка? Попробуйте

for k in mylist[0:n]:
     # do stuff with k

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

my_new_list = [blah(k) for k in mylist[0:n]]
...