Перебор фрагментов списка - PullRequest
       32

Перебор фрагментов списка

28 голосов
/ 26 августа 2009

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

На мой взгляд, это что-то вроде:

for list_of_x_items in fatherList:
    foo(list_of_x_items)

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


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

FatherList - это многоуровневый numpy.array, который я получаю из файла. Функция должна находить средние значения ряда (пользователь указывает длину ряда). Для усреднения я использую функцию mean(). Теперь для расширения вопроса:

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

например, если список имеет длину 10, а размер фрагмента равен 3, то десятый член списка сохраняется и добавляется в начало следующего списка.


Связанный:

Ответы [ 9 ]

61 голосов
/ 26 августа 2009

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

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

Например

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

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

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

Это маленький грязный трюк


Хорошо, я объясню, как это работает. Это будет сложно объяснить, но я буду стараться изо всех сил.

Сначала немного фона:

В 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() на исходном итераторе

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

21 голосов
/ 26 августа 2009

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

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)
8 голосов
/ 26 августа 2009

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

обновление вопроса: как изменить функция, которую вы предоставили для хранения дополнительные предметы и использовать их, когда следующий 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
7 голосов
/ 26 августа 2009

Используйте генератор:

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 над последовательностью lst, то есть производит перекрывающиеся фрагменты: [1,2,3], [2,3,4], [3,4,5], ... Не уверен, что это намерение ОП, однако.

7 голосов
/ 26 августа 2009

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

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)
5 голосов
/ 27 августа 2009

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

>>> 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

2 голосов
/ 21 марта 2018

Расширение ответа @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
2 голосов
/ 21 мая 2014

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

>>> 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.

2 голосов
/ 26 августа 2009

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

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)
...