Ответ 1
Используйте itertools.islice:
import itertools
l = range(20)
for i in itertools.islice(l,10,15):
print i
10
11
12
13
14
Из документа:
Сделайте итератор, который возвращает выбранные элементы из итеративного
Как правило, если вы хотите перебирать часть списка в Python, проще всего просто нарезать список.
# Iterate over everything except the first item in a list
#
items = [1,2,3,4]
iterrange = (x for x in items[1:])
Но оператор среза создает новый список, который даже не нужно делать во многих случаях. В идеале, мне нужна какая-то функция среза, которая создает генераторы, а не новые объекты списка. Нечто похожее на это может быть выполнено путем создания выражения генератора, которое использует range
для возврата только определенных частей списка:
# Create a generator expression that returns everything except
# the first item in the list
#
iterrange = (x for x, idx in zip(items, range(0, len(items))) if idx != 0)
Но это довольно громоздко. Мне интересно, есть ли лучший, более элегантный способ сделать это. Итак, какой самый простой способ разрезать список, чтобы вместо генерации было создано выражение генератора?
Используйте itertools.islice:
import itertools
l = range(20)
for i in itertools.islice(l,10,15):
print i
10
11
12
13
14
Из документа:
Сделайте итератор, который возвращает выбранные элементы из итеративного
Прежде чем я начну, чтобы быть ясным, правильный порядок выбора между подходами нарезки обычно:
allbutone = list(someiterable)[1:]
, например, allbutone = list(someiterable)[1:]
. Это проще и в большинстве случаев обычно быстрее, чем любой другой подход.list
элементов 10M, так что может возникнуть проблема с itertools.islice
), itertools.islice
обычно является правильным решением, поскольку оно достаточно простое, а затраты на производительность обычно не важны.islice
неприемлемо islice
(это добавляет некоторые накладные расходы на производство каждого элемента, хотя по общему признанию это довольно небольшой объем) и объем пропускаемых данных невелик, тогда как объем данных, которые нужно включить, огромен (например, OP сценарий пропуска одного элемента и сохранения остальных), продолжайте читать Если вы окажетесь в случае № 3, вы находитесь в сценарии, в котором способность islice
обойти начальные элементы (относительно) недостаточна, чтобы компенсировать дополнительные затраты на производство остальных элементов. В этом случае вы можете улучшить производительность, изменив свою проблему: от выбора всех элементов после n
до отказа от всех элементов до n
.
Для этого подхода вы вручную конвертируете свои входные данные в итератор, затем явно извлекаете и отбрасываете n
значений, а затем повторяете то, что осталось в итераторе (но без дополнительных islice
на каждый элемент islice
). Например, для ввода myinput = list(range(1, 10000))
ваши варианты выбора элементов от 1 до конца:
# Approach 1, OP approach, simple slice:
for x in myinput[1:]:
# Approach 2, Sebastian approach, using itertools.islice:
for x in islice(myinput, 1, None):
# Approach 3 (my approach)
myiter = iter(myinput) # Explicitly create iterator from input (looping does this already)
next(myiter, None) # Throw away one element, providing None default to avoid StopIteration error
for x in myiter: # Iterate unwrapped iterator
Если количество отбрасываемых элементов больше, вероятно, лучше позаимствовать рецепт consume
из документации itertools
:
def consume(iterator, n=None):
"Advance the iterator n-steps ahead. If n is None, consume entirely."
# Use functions that consume iterators at C speed.
if n is None:
# feed the entire iterator into a zero-length deque
collections.deque(iterator, maxlen=0)
else:
# advance to the empty slice starting at position n
next(islice(iterator, n, n), None)
что обобщает подходы для пропуска n
элементов:
# Approach 1, OP approach, simple slice:
for x in myinput[n:]:
# Approach 2, Sebastian approach, using itertools.islice:
for x in islice(myinput, n, None):
# Approach 3 (my approach)
myiter = iter(myinput) # Explicitly create iterator from input (looping does this already)
consume(myiter, n) # Throw away n elements
# Or inlined consume as next(islice(myiter, n, n), None)
for x in myiter: # Iterate unwrapped iterator
С точки зрения производительности, это выигрывает на значительную величину для большинства больших входных данных (исключение: сам range
на Python 3 уже оптимизирован для простой нарезки; простая нарезка не может быть разбита на реальных объектах range
). ipython3
(в CPython 3.6, 64-битная сборка Linux) иллюстрируют это (определение slurp
в настройке - это просто способ сделать подход с минимальными издержками на выполнение итеративным, поэтому мы минимизируем влияние того, что нам не интересно в):
>>> from itertools import islice
>>> from collections import deque
>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10000))
... slurp(r[1:])
...
65.8 μs ± 109 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)
>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10000))
... slurp(islice(r, 1, None))
...
70.7 μs ± 104 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)
>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10000))
... ir = iter(r)
... next(islice(ir, 1, 1), None) # Inlined consume for simplicity, but with islice wrapping to show generalized usage
... slurp(ir)
...
30.3 μs ± 64.1 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)
Очевидно, что дополнительная сложность моего решения, как правило, не стоит того, но для входных данных среднего размера (в данном случае 10 тыс. Элементов) выигрыш в производительности очевиден; islice
был худшим исполнителем (по небольшому количеству), обычная нарезка была немного лучше (что подтверждает мою точку зрения на то, что простая нарезка почти всегда является лучшим решением, когда у вас есть фактическая последовательность), а также "преобразовать в итератор, отбросить начальную, использовать Подход "покоя" выиграл в огромной сумме, условно говоря (значительно меньше, чем вдвое меньшее из решений).
Это преимущество не islice
для крошечных вводов, потому что фиксированные накладные расходы на загрузку/вызов iter
/next
, и особенно islice
, перевесят экономию:
>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... slurp(r[1:])
...
207 ns ± 1.86 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)
>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... slurp(islice(r, 1, None))
...
307 ns ± 1.71 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)
>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... ir = iter(r)
... next(islice(ir, 1, 1), None) # Inlined consume for simplicity, but with islice wrapping to show generalized usage
... slurp(ir)
...
518 ns ± 4.5 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)
>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... ir = iter(r)
... next(ir, None) # To show fixed overhead of islice, use next without it
... slurp(ir)
...
341 ns ± 0.947 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)
но, как вы можете видеть, даже для 10 элементов islice
-free не намного хуже; на 100 элементов islice
-free быстрее, чем у всех конкурентов, а на 200 элементов обобщенный next
+ islice
превосходит всех конкурентов (очевидно, он не islice
-free, учитывая 180 нс накладных расходов на islice
, но это компенсируется пропуском n
элементов как одного шага, вместо необходимости повторного вызова next
для пропуска более одного элемента). Обычный islice
редко побеждает в деле "пропусти несколько, сохраняй много" из-за накладных расходов на каждый элемент, требуемых оболочкой (он явно не побеждает стремительного нарезания микробенчмарков до тех пор, пока не наберется около 100 КБ элементов; это неэффективно для памяти, но неэффективно для ЦП) и это будет еще хуже (по сравнению с рвением нарезать) в случае "пропустить много, сохранить несколько".
Попробуйте itertools.islice:
http://docs.python.org/library/itertools.html#itertools.islice
iterrange = itertools.islice(items, 1, None)