Python: элегантный способ двойной / многократной итерации по одному и тому же списку - PullRequest
6 голосов
/ 15 мая 2009

Я написал немного кода, подобного следующему, чтобы сравнить элементы с другими элементами в списке далее. Есть ли более элегантный шаблон для такой двойной итерации?

jump_item_iter = (j for j in items if some_cond)
try:
    jump_item = jump_item_iter.next()
except StopIteration:
    return
for item in items:
    if jump_item is item:
        try:
            jump_item = jump_iter.next()
        except StopIteration:
            return
    # do lots of stuff with item and jump_item

Я не думаю, что "except StopIteration" очень элегантно

Edit:

Чтобы, надеюсь, стало понятнее, я хочу посетить каждый элемент в списке и связать его со следующим элементом в списке (jump_item), который удовлетворяет some_cond.

Ответы [ 17 ]

4 голосов
/ 15 мая 2009

Насколько я могу видеть, что любое из существующих решений работает на одном единственном снимке, возможно, бесконечном iter ator , все они, кажется, требуют iter способны .

Вот решение этой проблемы.

def batch_by(condition, seq):
    it = iter(seq)
    batch = [it.next()]
    for jump_item in it:
        if condition(jump_item):
            for item in batch:
                yield item, jump_item
            batch = []
        batch.append(jump_item)

Это будет легко работать на бесконечных итераторах:

from itertools import count, islice
is_prime = lambda n: n == 2 or all(n % div for div in xrange(2,n))
print list(islice(batch_by(is_prime, count()), 100))

Это напечатает первые 100 целых чисел с простым числом, которое следует за ними.

1 голос
/ 15 мая 2009

Написать функцию генератора:

def myIterator(someValue):
    yield (someValue[0], someValue[1])

for element1, element2 in myIterator(array):
     # do something with those elements.
1 голос
/ 15 мая 2009

Я понятия не имею, что делает compare(), но в 80% случаев вы можете сделать это с помощью тривиального словаря или пары словарей. Перемещение по списку - это своего рода линейный поиск. Линейный поиск - насколько это возможно - всегда должен заменяться либо прямой ссылкой (т. Е. Диктом), либо поиском по дереву (с использованием модуля bisect).

1 голос
/ 15 мая 2009

Как насчет этого?

paired_values = []
for elmt in reversed(items):
    if <condition>:
        current_val = elmt
    try:
        paired_values.append(current_val)
    except NameError:  # for the last elements of items that don't pass the condition
        pass
paired_values.reverse()

for (item, jump_item) in zip(items, paired_values):  # zip() truncates to len(paired_values)
    # do lots of stuff

Если первый элемент элементов совпадает, то он используется как jump_item. Это единственная разница с вашим исходным кодом (и вам может понадобиться такое поведение).

1 голос
/ 15 мая 2009

Следующий итератор экономит время и память:

def jump_items(items):
    number_to_be_returned = 0
    for elmt in items:
        if <condition(elmt)>:
            for i in range(number_to_be_returned):
                yield elmt
            number_to_be_returned = 1
        else:
            number_to_be_returned += 1

for (item, jump_item) in zip(items, jump_items(items)):
    # do lots of stuff

Обратите внимание, что вы, возможно, захотите установить для первого числа_в_безврат равное 1 ...

0 голосов
/ 15 мая 2009

Может быть, уже слишком поздно, но как насчет:

l = [j for j in items if some_cond]
for item, jump_item in zip(l, l[1:]):
    # do lots of stuff with item and jump_item

Если l = [j для j в диапазоне (10), если j% 2 == 0], то итерация окончена: [(0, 2), (2, 4), (4, 6), (6 , 8)].

0 голосов
/ 15 мая 2009

Еще лучше, используя itertools.groupby:

def h(lst, cond):
  remain = lst
  for last in (l for l in lst if cond(l)):
    group = itertools.groupby(remain, key=lambda x: x < last)
    for start in group.next()[1]:
      yield start, last
    remain = list(group.next()[1])

Использование: lst = диапазон (10) конд = лямбда х: х% 2 список печати (h (lst, cond))

напечатает

[(0, 1), (1, 3), (2, 3), (3, 5), (4, 5), (5, 7), (6, 7), (7, 9), (8, 9)]
0 голосов
/ 15 мая 2009

Только с итераторами

def(lst, some_cond):
      jump_item_iter = (j for j in lst if som_cond(j))
      pairs = itertools.izip(lst, lst[1:])
      for last in jump_item_iter:
        for start, start_next in itertools.takewhile(lambda pair: pair[0] < last, pairs):
          yield start, last
        pairs = itertools.chain([(start_next, 'dummy')], pairs)

с вводом: range (10) и some_cond = lambda x: x% 2 дает [(0, 1), (1, 3), (2, 3), (3, 5), (4, 5), (5, 7), (6, 7), (7, 9), (8, 9)] (то же, что ваш пример)

0 голосов
/ 15 мая 2009
for i in range( 0, len( items ) ):
    for j in range( i+1, len( items ) ):
        if some_cond:
            #do something
            #items[i] = item, items[j] = jump_item
0 голосов
/ 15 мая 2009

Вы можете поместить всю итерацию в одну структуру try, так будет проще:

jump_item_iter = (j for j in items if some_cond)
try:
    jump_item = jump_item_iter.next()
    for item in items:
        if jump_item is item:
            jump_item = jump_iter.next()

    # do lots of stuff with item and jump_item

 except StopIteration:
     pass
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...