Pythonic способ определения, является ли текущий элемент первым или последним элементом генератора? - PullRequest
4 голосов
/ 12 мая 2011

Я прохожу через генератор, как Pythonic определяет, является ли текущий элемент первым или последним элементом генератора, учитывая, что они нуждаются в особом уходе?

спасибо

в основном генерирует теги, поэтому у меня есть такие элементы, как

<div class="first">1</div>
<div>...</div>
<div class="last">n</div>

, поэтому я хотел бы сохранить последний элемент в цикле?

Ответы [ 7 ]

5 голосов
/ 12 мая 2011

Вот генератор, похожий на перечисление, который пропускает один;он возвращает -1 для последнего элемента.

>>> def annotate(gen):
...     prev_i, prev_val = 0, gen.next()
...     for i, val in enumerate(gen, start=1):
...         yield prev_i, prev_val
...         prev_i, prev_val = i, val
...     yield '-1', prev_val
>>> for i, val in annotate(iter(range(4))):
...     print i, val
... 
0 0
1 1
2 2
-1 3

Он не может сказать, является ли переданный ему генератор "свежим" или нет, но он все равно сообщает вам, когда конец близок:

>>> used_iter = iter(range(5))
>>> used_iter.next()
0
>>> for i, val in annotate(used_iter):
...     print i, val
... 
0 1
1 2
2 3
-1 4

Как только итератор израсходован, он поднимает StopIteration как обычно.

>>> annotate(used_iter).next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in annotate
StopIteration
3 голосов
/ 04 января 2014

То, как я это делаю, похоже на некоторые другие ответы здесь - но я делаю это так, как предпочтение.Может быть, это будет соответствовать вашим предпочтениям.

С моей функцией ниже, я могу написать код, подобный этому:

values = [10, 11, 12, 13]
for i, val, isfirst, islast in enumerate2(values):
  if isfirst:
    print 'BEGIN...', val
  elif islast:
    print val, '... END'
  else:
    print val

Вот определение функции:

def enumerate2(iterable_):
  it = iter(iterable_)
  try:
    e = it.next()
    isfirst = True
    i = 0
    try:
      while True:
        next_e = it.next()
        yield (i, e, isfirst, False)
        i += 1
        isfirst = False
        e = next_e
    except StopIteration:
      yield (i, e, isfirst, True)
  except StopIteration:
    pass
2 голосов
/ 12 мая 2011

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

1 голос
/ 12 мая 2011

Если вас беспокоят потенциально большие коллекции, создаваемые динамически, так что вы не хотите временно помещать их в одну структуру данных, вот другой способ:

FLAGMASK_FIRST = 1
FLAGMASK_LAST = 2

def flag_lastfirst(collection):
    first_flag = FLAGMASK_FIRST
    first = True
    index = 0
    for element in collection:
        if not first:
            yield index, first_flag, current
            index += 1
            first_flag = 0
        current = element
        first = False
    if not first:
        yield index, first_flag | FLAGMASK_LAST, current

l = [1, 2, 3, 4]
for k in flag_lastfirst(l):
    print(k)

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

Содержимое кортежа:

  • t[0] = индекс на основе 0
  • t[1] = побитовые флаги, FLAGMASK_FIRST присутствует, если элемент является первым элементом, FLAGMASK_LAST присутствует, если элемент является последним элементом
  • t[2] = Исходный элемент из исходной коллекции

Пример выходных данных из приведенного выше кода:

 +-- 0-based index
 v
(0, 1, 1)
(1, 0, 2)
(2, 0, 3)
(3, 2, 4)
    ^  ^
    |  +-- the element from the original collection
    |
    +-- 1 means first, 2 means last,
        3 means both first and last, 0 is everything else

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

1 голос
/ 12 мая 2011

Конечно, это нарушает все преимущества генератора, но если ваша итерация невелика, вы должны использовать:

list(gener)[1:-1]
1 голос
/ 12 мая 2011

Превратите это в последовательность, например:

>>> gen = (x for x in range(5))
>>> L = list(gen)
>>> L[0]
0
>>> L[-1]
4
>>>

Если вам нужно сделать это во время цикла:

>>> gen = (x for x in range(5))
>>> L = list(gen)
>>> for idx, item in enumerate(L):
...    if idx == 0:
...        print(u'{item} is first'.format(item=item))
...    if idx == len(L) - 1:
...        print(u'{item} is last'.format(item=item))
...
0 is first
4 is last
>>>

Ясно, что это не решение, если вы - тот, кто создал генератор, и вам нужно, чтобы он оставался таким (для экономии памяти), но если вам все равно, это больше Pythonic как такового, чем установка флагов (что в лучшем случае неявно, так как он полагается на последний элемент во время сохранения итерации), и enumerate не приблизит вас к поиску последнего элемента.

1 голос
/ 12 мая 2011

Ну, что касается первого элемента:

for n, item in enumerate(generator()):
  if n == 0:
    # item is first
# out of the loop now: item is last
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...