Цикл Python: идиоматическое сравнение последовательных элементов в списке

Мне нужно перебрать список объектов, сравнивая их следующим образом: 0 и 1, 1 и 2, 2 и 3 и т. д. (я использую pysvn для извлечения списка различий). зацикливаюсь на индексе, но мне все время интересно, есть ли какой-нибудь способ сделать это, который был бы более идиоматичен. Это Питон; разве я не должен использовать итераторы каким-то умным способом? Простой цикл по индексу кажется довольно ясным, но мне интересно, есть ли более выразительный или краткий способ сделать это.

for revindex in xrange(len(dm_revisions) - 1):
    summary = \
        svn.diff_summarize(svn_path,
                          revision1=dm_revisions[revindex],
                          revision2 = dm_revisions[revindex+1])

person Allan Anderson    schedule 28.01.2010    source источник
comment
Лично я чувствую, что на самом деле могут существовать более умные способы сделать это, но цикл по индексу — самый простой способ сделать это.   -  person Nikwin    schedule 28.01.2010
comment
+1 за хорошее описание, которое помогло мне найти решение.   -  person xtian    schedule 30.05.2014


Ответы (5)


Это называется скользящим окном. В itertools документации есть пример, который это делает. Вот код:

from itertools import islice

def window(seq, n=2):
    "Returns a sliding window (of width n) over data from the iterable"
    "   s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...                   "
    it = iter(seq)
    result = tuple(islice(it, n))
    if len(result) == n:
        yield result    
    for elem in it:
        result = result[1:] + (elem,)
        yield result

Что это, можно сказать так:

for r1, r2 in window(dm_revisions):
    summary = svn.diff_summarize(svn_path, revision1=r1, revision2=r2)

Конечно, вас интересует только случай, когда n=2, поэтому вы можете обойтись чем-то гораздо более простым:

def adjacent_pairs(seq):
    it = iter(seq)
    a = it.next()
    for b in it:
        yield a, b
        a = b

for r1, r2 in adjacent_pairs(dm_revisions):
    summary = svn.diff_summarize(svn_path, revision1=r1, revision2=r2)
person Jason Orendorff    schedule 28.01.2010
comment
да. (Слава богу, у нас есть ограничение в 15 символов. В противном случае я мог бы просто сказать «да» в ответ на вопрос «да» или «нет».) - person Jason Orendorff; 28.01.2010
comment
Здорово. Это работает и, я думаю, более понятно. Я могу дать ревизиям информативные имена, чтобы люди знали, что используется дальше в сценарии. Я тоже ценю то, что все это прописано, даже если я использовал tee и izip. - person Allan Anderson; 29.01.2010

Я бы, наверное, сделал:

import itertools
for rev1, rev2 in zip(dm_revisions, itertools.islice(dm_revisions, 1, None)):
    summary = svn.diff_sumeraize(svn_python, revision1=rev, revision2=rev2)

Что-то более умное и не касающееся самих итераторов, вероятно, можно было бы сделать с помощью

person Alex Gaynor    schedule 28.01.2010
comment
Это было первое, что пришло мне в голову, это более функциональный подход. По сути, вы застегиваете список с остальной его частью (в результате получается v1,v2,v2,v3,v3...), а затем берете пары из двух из полученного списка (v1,v2)(v2,v3) (v3,v4)... - person RHSeeger; 28.01.2010
comment
Имеет смысл и кажется довольно лаконичным. Как насчет использования izip, как описано здесь: docs.python.org/library/itertools.html ? - person Allan Anderson; 28.01.2010

Размещено так много сложных решений, почему бы не сделать их простыми?

myList = range(5)

for idx, item1 in enumerate(myList[:-1]):
    item2 = L[idx + 1]
    print item1, item2

>>> 
0 1
1 2
2 3
3 4
person truppo    schedule 29.01.2010

Сохранить предыдущее значение в переменной. Инициализируйте переменную со значением, которое вы вряд ли найдете в обрабатываемой вами последовательности, чтобы вы могли знать, находитесь ли вы в первом элементе. Сравните старое значение с текущим значением.

person Ignacio Vazquez-Abrams    schedule 28.01.2010
comment
Ах, это звучит как интересный альтернативный способ сделать это - хотя и не такой питонический, как создание причудливого попарного итератора :) - person Allan Anderson; 29.01.2010
comment
На самом деле, причудливый попарный итератор был бы больше похож на Haskellish/Lispish, хотя и работал бы в Python. - person Ignacio Vazquez-Abrams; 30.01.2010
comment
Интересно; думаю, мне нужно больше узнать обо всех трех типах выражений. - person Allan Anderson; 04.02.2010

Для этой цели можно использовать сокращение, если вы позаботитесь о том, чтобы оставить копию текущего элемента в результате функции сокращения.

def diff_summarize(revisionList, nextRevision):
    '''helper function (adaptor) for using svn.diff_summarize with reduce'''
    if revisionList:
        # remove the previously tacked on item
        r1 = revisionList.pop()
        revisionList.append(svn.diff_summarize(
            svn_path, revision1=r1, revision2=nextRevision))
    # tack the current item onto the end of the list for use in next iteration
    revisionList.append(nextRevision)
    return revisionList

summaries = reduce(diff_summarize, dm_revisions, [])

EDIT: Да, но никто не говорил, что результат функции в reduce должен быть скалярным. Я изменил свой пример, чтобы использовать список. По сути, последний элемент всегда является предыдущей версией (кроме первого прохода), а все предыдущие элементы являются результатом вызова svn.diff_summarize. Таким образом, вы получите список результатов в качестве окончательного вывода...

EDIT2: Да, код действительно был сломан. У меня есть рабочий манекен:

>>> def compare(lst, nxt):
...    if lst:
...       prev = lst.pop()
...       lst.append((prev, nxt))
...    lst.append(nxt)
...    return lst
...
>>> reduce(compare, "abcdefg", [])
[('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'e'), ('e', 'f'), ('f', 'g'), 'g']

Как видите, это было протестировано в оболочке. Вы захотите заменить (prev, nxt) в lst.append вызове compare, чтобы фактически добавить сводку вызова к svn.diff_summarize.

>>> help(reduce)
Help on built-in function reduce in module __builtin__:

reduce(...)
    reduce(function, sequence[, initial]) -> value

    Apply a function of two arguments cumulatively to the items of a sequence,
    from left to right, so as to reduce the sequence to a single value.
    For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
    ((((1+2)+3)+4)+5).  If initial is present, it is placed before the items
    of the sequence in the calculation, and serves as a default when the
    sequence is empty.
person Daren Thomas    schedule 28.01.2010
comment
Нет, функция reduce применяет функцию к каждому элементу последовательности и накопленному сокращенному значению, а не к каждому элементу и его предшественнику. - person Ian Clelland; 28.01.2010
comment
Я считаю, что ОП просто хочет сравнить последовательные элементы. Что делает сокращение, так это работает с первыми двумя элементами, берет результат этого и выполняет операцию с результатом и следующим элементом, и повторяет это до тех пор, пока не останется элементов. - person Nikwin; 28.01.2010
comment
Конечно, но это лишь незначительное отличие — вы по-прежнему сравниваете данные одной итерации с данными следующей итерации. Смотрите обновленный код. - person Daren Thomas; 28.01.2010
comment
Этот код довольно сломан, похоже. Вы можете использовать сокращение для этого: pastie.org/798394, но я бы не стал рекомендовать его. Он кажется излишне непрозрачным. - person Jason Orendorff; 28.01.2010