модуле itertools.реализация islice-эффективное нарезание списка
ранее я пытался ответить на вопрос, где я хотел повторить срез списка как можно эффективнее.
for x in lst[idx1:]:
не идеально, поскольку он создает копию (в общем, это O(n)
). Моей следующей мыслью было использовать itertools.islice
. Но если вы посмотрите на документацию, то окажется, что islice
будем называть next
пока он не найдет индекс, который он ищет, в какой момент он начнет давать значения. Это тоже O(n)
. Кажется, что есть оптимизация, которая доступна здесь, если объект передан в islice
это list
или tuple
-- кажется, что вы можете перебирать "срез" напрямую (в C), фактически не делая копию. Мне было любопытно, если эта оптимизация в источник, но я ничего не нашел. Я не очень хорошо знаком с C и исходным деревом python, поэтому вполне возможно, что я пропустил его.
мой вопрос такой:
есть ли способ перебирать список "срез", не делая копию среза списка и не сжигая кучу нежелательных элементов (в оптимизированной реализации C)?
Я хорошо знаю, что я мог бы написать свой собственный генератор для этого (очень наивно, не учитывая тот факт, что многие из аргументов должны быть необязательными и т. д.):
def myslice(obj,start,stop,stride):
for i in xrange(start,stop,stride):
yield obj[i]
но это определенно не будет бить оптимизированную реализацию C.
Если вам интересно почему мне нужно это просто зацикливание на срезе напрямую, рассмотрим разницу между:
takewhile(lambda x: x == 5, lst[idx:]) #copy's the tail of the list unnecessarily
и
takewhile(lambda x: x == 5, islice(lst,idx,None)) #inspects the head of the list unnecessarily
и наконец:
takewhile(lambda x: x == 5, magic_slice(lst,idx,None)) #How to create magic_slice???
4 ответов
есть ли способ перебирать список "срез", не делая копию среза списка и не сжигая кучу нежелательных элементов (в оптимизированной реализации C)?
Да есть, если вы пишете, что реализация C. на Cython делает это очень легко.
cdef class ListSlice(object):
cdef object seq
cdef Py_ssize_t start, end
def __init__(self, seq, Py_ssize_t start, Py_ssize_t end):
self.seq = seq
self.start = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start == self.end:
raise StopIteration()
r = self.seq[self.start]
self.start += 1
return r
Я думаю, стоит упомянуть, что numpy-срезы не копируются (они создают представление на базовый массив). Поэтому, если вы можете использовать массивы NumPy для своих данных, это решит проблему. Кроме того, вы можете получить дополнительные улучшения производительности за счет векторизации.
Если вы используете PyPy (который вы можете, так как вы заботитесь о производительности), они оптимизируют нарезку строк, чтобы не копировать:http://doc.pypy.org/en/latest/interpreter-optimizations.html
islice
является функцией от itertools
модуль, поэтому он работает (и определенно должен работать) с iterator
s в общем, не только с list
s. Таким образом, вы не можете найти свою оптимизацию в itertools
исходный код, потому что он должен работать с любой итератор.
правильный подход в вашем случае это:
def magic_slice(lst, start, end=None):
for pos in xrange(start, (end or len(lst)):
yield lst[pos]
takewhile
будет называть ваш генератор "один за другим", и он будет yield
новые значения-та же "скорость", что и для общего списка ходьбы + xrange
итерации. Так накладные расходы при такой реализации минимальны. Если вам нужно больше - вы можете переписать эту функцию на C, но я не вижу много преимуществ для этого.