2009-05-15 2 views
6

Я написал немного кода, как показано ниже, чтобы сравнить элементы с другими элементами, включенными в список. Есть ли более элегантный образец для такого рода двойной итерации?Python: Элегантный способ двойной/многократной итерации по тому же списку

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.

+0

Вместо кода (или в дополнение к нему) укажите свой вход/выход, который вы ожидаете, пожалуйста. –

+0

Правильно ли говорить, что если элементы = диапазон (10) и some_cond = лямбда x: x% 2, то выход должен быть: [(0, 1), (1, 3), (2, 3) , (3, 5), (4, 5), (5, 7), (6, 7), (7, 9), (8, 9)] – odwl

+0

Да, odwl, ожидаемый результат. – EoghanM

ответ

0

Я понятия не имею, что вы пытаетесь сделать с этим кодом. Но я на 99% уверен, что что бы это ни было, возможно, можно сделать в 2 строках. У меня также возникает ощущение, что оператор '==' должен быть оператором 'is', иначе что делает функция compare()? И что произойдет, если элемент, возвращенный со второго вызова jump_iter.next, также равен «item»? Похоже, что алгоритм поступил бы неправильно, так как вы сравните второй, а не первый.

+0

Спасибо, я обновил '==', чтобы быть «есть», это более правильно. Функция сравнения является заполнителем для большого блока кода, который не имеет большого значения для вопроса. – EoghanM

0

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

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

Полагаю, я мог бы поставить вопрос: Я хочу посетить каждый элемент в списке и соединить его со следующим элементом далее в списке, который соответствует определенному условию. Я обновлю вопрос – EoghanM

0

Так что вы хотите сравнить пары элементов в одном списке, второй элемент пары, имеющий для удовлетворения некоторых условий. Обычно, когда вы хотите сравнить пары в использовании списка zip (или itertools.izip):

for item1, item2 in zip(items, items[1:]): 
    compare(item1, item2) 

Выяснить, как в соответствии с Вашими some_cond в этом :)

+0

Я не сравниваю элементы [i] с элементами [i + 1], если 'some_cond' является ложным, item2 может пропустить несколько мест в списке. – EoghanM

+0

Вот почему я говорил о том, как правильно подобрать 'some_cond'. что-то вроде 'others = [item для элемента в элементах [1:] if some_cond]', а затем 'zip (items, others)'. –

+0

Разве это не та часть, которую он должен «определить» на самой твердой части? И почему ваш подход подходит для этого? – nikow

1

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

+0

Я удалил ссылку на compare(), поскольку это было путано - я на самом деле делаю много вещей с помощью item и jump_item - сравнение было заполнителем. Это линейный поиск, поскольку 'items' определенно является последовательностью. – EoghanM

0

Вы в основном пытаетесь сравнить каждый элемент в итераторе с любым другим элементом в исходном списке?

На мой взгляд, это должен быть случай использования двух циклов, вместо того, чтобы пытаться поместить его в один.

 

filtered_items = (j for j in items if some_cond) 
for filtered in filtered_items: 
    for item in items: 
     if filtered != item: 
      compare(filtered, item) 
 
+0

Я пытаюсь избежать вложенных циклов, так как мне нужно только один раз посетить каждый элемент. – EoghanM

0

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

for i, item in enumerate(items): 
    for next_item in items[i+1:]: 
     if some_cond(next_item): 
      break 
    # do some stuff with both items 

Недостаток заключается в том, что вы проверяете условие NEXT_ITEM несколько раз. Но вы можете легко оптимизировать это:

cond_items = [item if some_cond(item) else None for item in items] 
for i, item in enumerate(items): 
    for next_item in cond_items[i+1:]: 
     if next_item is not None: 
      break 
    # do some stuff with both items 

Однако оба решения несут больше накладных расходов, чем исходное решение вопроса. И когда вы начинаете использовать счетчики, чтобы обойти это, я думаю, что лучше использовать интерфейс итератора напрямую (как в исходном решении).

1

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

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. Это единственное отличие от вашего исходного кода (и вы можете захотеть этого поведения).

0

Мой первый ответ был неправильным, потому что я не совсем понял, чего вы пытаетесь достичь. Поэтому, если я правильно понимаю (на этот раз, надеюсь), вы хотите, чтобы основной for item in items: «преследовал» после итератора, который отфильтровывает некоторые элементы. Ну, вы не можете сделать ничего, кроме, может быть, обернуть это в генератор chase_iterator(iterable, some_cond), который сделает ваш основной код немного более удобочитаемым.

Возможно, что более читаемым подход будет являться «накопительный подход» (если заказ из сравнения() не имеет значения), как:

others = [] 
for item in items: 
    if some_cond(item): 
     for other in others: 
      compare(item, other) 
     others = [] 
    else: 
     others.append(item) 

(мужчина, я начинаю ненавидеть Переполнение стека ... слишком привыкание ...)

1

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

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 

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

0
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

С помощью всего итераторы

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) 

с входным: диапазон (10) и some_cond = лямбда-х:% 2 дает [(0, 1), (1, 3), (4, 5), (5, 7), (6, 7), (7, 9), (8, 9)] (то же, что и ваш пример)

+0

Здесь даже «партия» (см. Решение Ants Aasma) является итерируемой. – odwl

0

Даже лучше, используя 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 список печати (ч (LST, конд))

напечатает

[(0, 1), (1, 3), (2, 3), (3, 5), (4, 5), (5, 7), (6, 7), (7, 9), (8, 9)] 
0

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

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

никогда не поздно, но это не то, что хочет OP – SilentGhost

+0

С элементами = диапазон (10) и some_cond = лямбда x% 2, Это даст (1 3), (3 5), (5,7), (7,9) Я думаю, что это не вопрос, который вам не хватает, например, например, – odwl

+0

? первая строка получает все элементы, которые удовлетворяют some_cond, а цикл - над парами таких элементов. т.е. каждый элемент, который удовлетворяет cond, сопряжен со следующим элементом, который удовлетворяет cond. –

1

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

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

for element1, element2 in myIterator(array): 
    # do something with those elements. 
4

Насколько я могу видеть любого из существующих решений работать на общий один выстрел, possiboly бесконечного ITER АТОРА, все они, кажется, требует ITER способный.

Heres решение для этого.

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 целых чисел с простым числом, что следует за ними.

+0

Похоже, очень хорошее решение. Но пакет не является итератором. – odwl

0

Вы можете написать тело цикла как:

import itertools, functools, operator 

for item in items: 
    jump_item_iter = itertools.dropwhile(functools.partial(operator.is_, item), 
             jump_item_iter) 

    # do something with item and jump_item_iter 

dropwhile возвращает итератор, который пропускает через все те, которые соответствуют условию (здесь «is item»).

+0

Вам не хватает строки 'jump_item_iter = items' перед телом цикла? – EoghanM

+0

Он должен быть таким же, как в вопросе перед телом цикла. т.е. jump_item_iter = "(j для j в элементах, если some_cond)" – Brian

+0

Я думаю, что это не сработает, так как не существует способа узнать, когда вызывать jump_item_iter.next() и когда повторно использовать предыдущее значение jump_item_iter.next() – EoghanM

0

Вы могли бы сделать что-то вроде:

import itertools 

def matcher(iterable, compare): 
    iterator= iter(iterable) 
    while True: 
     try: item= iterator.next() 
     except StopIteration: break 
     iterator, iterator2= itertools.tee(iterator) 
     for item2 in iterator2: 
      if compare(item, item2): 
       yield item, item2 

но это довольно сложный (и на самом деле не очень эффективно), и было бы проще, если бы вы просто сделали

items= list(iterable) 

, а затем просто написать две петли над items.

Очевидно, что это не будет работать с бесконечными итерами, но ваша спецификация может работать только на конечных итерациях.

Смежные вопросы