Итерация по срезам списка

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

В моем понимании это что-то вроде:

for list_of_x_items in fatherList:
    foo(list_of_x_items)

есть ли способ правильно определить list_of_x_items или какой-то другой способ сделать это с помощью Python 2.5?


edit1: разъяснения термины "разбиение" и "раздвижное окно" применимы к моей задаче, но я не эксперт. Поэтому я объясню проблему немного глубже и добавить к вопросу:

В fatherList является многоуровневым и NumPy.массив я получаю из файла. Функция должна найти средние ряды (пользователь предоставляет длину ряда) для усреднения я использую

9 ответов


ответ на последнюю часть вопроса:

обновление вопроса: Как изменить функция, которую вы предоставили для хранения дополнительные детали и используют их когда следующий fatherList подается в функции?

Если вам нужно сохранить состояние, то вы можете использовать объект для этого.

class Chunker(object):
    """Split `iterable` on evenly sized chunks.

    Leftovers are remembered and yielded at the next call.
    """
    def __init__(self, chunksize):
        assert chunksize > 0
        self.chunksize = chunksize        
        self.chunk = []

    def __call__(self, iterable):
        """Yield items from `iterable` `self.chunksize` at the time."""
        assert len(self.chunk) < self.chunksize
        for item in iterable:
            self.chunk.append(item)
            if len(self.chunk) == self.chunksize:
                # yield collected full chunk
                yield self.chunk
                self.chunk = [] 

пример:

chunker = Chunker(3)
for s in "abcd", "efgh":
    for chunk in chunker(s):
        print ''.join(chunk)

if chunker.chunk: # is there anything left?
    print ''.join(chunker.chunk)

выход:

abc
def
gh

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

list_of_slices = zip(*(iter(the_list),) * slice_size)
>>> zip(*(iter(range(10)),) * 3)
[(0, 1, 2), (3, 4, 5), (6, 7, 8)]

если количество элементов не делится на размер среза, и вы хотите заполнить список ни с одним Вы можете сделать это:

>>> map(None, *(iter(range(10)),) * 3)
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]

это грязный трюк


хорошо, я объясню, как это работает. Будет сложно объяснить, но я постараюсь.

сначала немного предыстории:

In Python вы можете умножить список на такое число:

[1, 2, 3] * 3 -> [1, 2, 3, 1, 2, 3, 1, 2, 3]
([1, 2, 3],) * 3 -> ([1, 2, 3], [1, 2, 3], [1, 2, 3])

и итератор объект можно потреблять один раз так:

>>> l=iter([1, 2, 3])
>>> l.next()
1
>>> l.next()
2
>>> l.next()
3

на zip функция возвращает список кортежей, где i-й кортеж содержит i-й элемент из каждой последовательности аргументов или итераций. Например:

zip([1, 2, 3], [20, 30, 40]) -> [(1, 20), (2, 30), (3, 40)]
zip(*[(1, 20), (2, 30), (3, 40)]) -> [[1, 2, 3], [20, 30, 40]]

* перед zip используется для распаковки аргументов. Вы можете найти более подробную информацию здесь. Так что

zip(*[(1, 20), (2, 30), (3, 40)])

фактически эквивалентно

zip((1, 20), (2, 30), (3, 40))

но работает с переменным количеством аргументов

теперь вернемся к хитрости:

list_of_slices = zip(*(iter(the_list),) * slice_size)

iter(the_list) -> преобразовать список в итератор

(iter(the_list),) * N - > создаст ссылку N на итератор the_list.

zip(*(iter(the_list),) * N) - > будет кормить этот список итераторов в zip. Которые, в свою очередь, сгруппируют их в N кортежей. Но так как все N элементов фактически являются ссылками на один и тот же итератор iter(the_list) результатом будут повторные вызовы next() на оригинальный итератор

надеюсь, это все объясняет. Я советую вам пойти с более простым для понимания решением. Я только хотел упомянуть об этом трюке, потому что он мне нравится.


Если вы хотите иметь возможность использовать любую итерацию, вы можете использовать следующие функции:

from itertools import chain, islice

def ichunked(seq, chunksize):
    """Yields items from an iterator in iterable chunks."""
    it = iter(seq)
    while True:
        yield chain([it.next()], islice(it, chunksize-1))

def chunked(seq, chunksize):
    """Yields items from an iterator in list chunks."""
    for chunk in ichunked(seq, chunksize):
        yield list(chunk)

вы имеете в виду что-то вроде:

def callonslices(size, fatherList, foo):
  for i in xrange(0, len(fatherList), size):
    foo(fatherList[i:i+size])

Если это примерно функциональность, которую вы хотите, вы можете, если хотите, одеть его немного в генератор:

def sliceup(size, fatherList):
  for i in xrange(0, len(fatherList), size):
    yield fatherList[i:i+size]

и затем:

def callonslices(size, fatherList, foo):
  for sli in sliceup(size, fatherList):
    foo(sli)

использовать генератор:

big_list = [1,2,3,4,5,6,7,8,9]
slice_length = 3
def sliceIterator(lst, sliceLen):
    for i in range(len(lst) - sliceLen + 1):
        yield lst[i:i + sliceLen]

for slice in sliceIterator(big_list, slice_length):
    foo(slice)

sliceIterator реализует "скользящее окно" ширины sliceLen над squence lst, т. е. он производит перекрывающиеся ломтики: [1,2,3], [2,3,4], [3,4,5], ... Не уверен, что это намерение OP.


Я не уверен, но кажется, вы хотите сделать то, что называется скользящей средней. numpy предоставляет средства для этого (функция свертки).

>>> x = numpy.array(range(20))
>>> x
    array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19])    
>>> n = 2 # moving average window
>>> numpy.convolve(numpy.ones(n)/n, x)[n-1:-n+1]
array([  0.5,   1.5,   2.5,   3.5,   4.5,   5.5,   6.5,   7.5,   8.5,
         9.5,  10.5,  11.5,  12.5,  13.5,  14.5,  15.5,  16.5,  17.5,  18.5])

хорошая вещь в том, что она хорошо приспосабливает различные схемы взвешивания (просто измените numpy.ones(n) / n к чему-то еще).

вы можете найти полный материал здесь: http://www.scipy.org/Cookbook/SignalSmooth


ваш вопрос может использовать более подробную информацию, но как насчет:

def iterate_over_slices(the_list, slice_size):
    for start in range(0, len(the_list)-slice_size):
        slice = the_list[start:start+slice_size]
        foo(slice)

для почти одного лайнера (после itertools import) в духе ответа Нади, касающегося не-куска делимых размеров без заполнения:

>>> import itertools as itt
>>> chunksize = 5
>>> myseq = range(18)
>>> cnt = itt.count()
>>> print [ tuple(grp) for k,grp in itt.groupby(myseq, key=lambda x: cnt.next()//chunksize%2)]
[(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14), (15, 16, 17)]

если вы хотите, вы можете избавиться от itertools.count() требование с помощью enumerate(), С довольно уродливым:

[ [e[1] for e in grp] for k,grp in itt.groupby(enumerate(myseq), key=lambda x: x[0]//chunksize%2) ]

(в этом примере enumerate() было бы лишним, но не все последовательности являются аккуратными диапазонами, как это, очевидно)

далеко не так аккуратно, как некоторые другие ответы, но полезно, в крайнем случае, особенно если уже импортируется itertools.


расширение ответа @Ants Aasma: в Python 3.7 обработка StopIteration исключение изменить (по PEP-479). Совместимая версия будет:

from itertools import chain, islice

def ichunked(seq, chunksize):
    it = iter(seq)
    while True:
        try:
            yield chain([next(it)], islice(it, chunksize - 1))
        except StopIteration:
            return