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

Я столкнулся со случаем использования перечислителя для выбранных элементов, формирующих итерируемый (т.е. последовательность или итератор или аналогичный) и хотите, чтобы возвращались исходные индексы вместо count по умолчанию, начиная с 0 и заканчивая len(iterable) - 1.

Очень наивным подходом было бы объявление нового объекта-генератора с именем _enumerate()

>>> def _enumerate(iterable, offset = 0, step = 1):
    index = offset
    for element in iterable:
        yield index, element
        index += step

... новый объект списка months.

>>> months = ["January", "February", "March", "April", "May", "June",
              "July", "August", "September", "October", "November", "December"]

Использование встроенной функции enumerate в Python даст следующие выходные данные для среза [5::2]:

>>> for index, element in enumerate(months[5::2]):
    print(index, element)


    0 June
    1 August
    2 October
    3 December

Ожидаемый вывод нашего собственного счетчика _enumerate снова для среза [5::2]:

>>> for index, element in _enumerate(months[5::2], offset = 5, step = 2):
    print(index, element)


    5 June
    7 August
    9 October
    11 December

Знаете ли вы какие-нибудь лучшие, более питонические и более читабельные решения? :)


person elegent    schedule 29.07.2015    source источник
comment
Ваш ответ питонический и достаточно читаемый.   -  person LittleQ    schedule 29.07.2015
comment
Как насчет i*step+offset? Должен также дать вам желаемые индексы...   -  person swenzel    schedule 29.07.2015
comment
Вы могли бы избежать повторения, если бы _enumerate также выполнял нарезку (т. е. называл бы это _enumerate(month, offset=5, step=2), но это потребовало бы, чтобы months был списком, чтобы быть эффективным.   -  person skyking    schedule 29.07.2015
comment
@swenzel: Куда вы вставляете index * step + offset?   -  person elegent    schedule 29.07.2015
comment
@skyking: Да. Спасибо, это хороший момент :)   -  person elegent    schedule 29.07.2015
comment
Везде, где вам нужен ваш реальный index. Например. print(index*2+5, element) с обычным перечислением также даст вам 5 июня, 7 августа... Или вы можете поставить index = index*step+offset в качестве первой строки в цикле.   -  person swenzel    schedule 29.07.2015
comment
@swenzel: О да :) Да, я глупый. Это хорошее решение! Спасибо :)   -  person elegent    schedule 29.07.2015


Ответы (4)


Вот мой комментарий в качестве ответа ;)

months = ["January", "February", "March", "April", "May", "June",
          "July", "August", "September", "October", "November", "December"]

offset = 5
step = 2
for index, element in enumerate(months[offset::step]):

    # recalculate original index
    index = offset + index*step

    # actually repetition of the month is trivial,
    # but I put it just to show that the index is right
    print(index, element, months[index])

Отпечатки:

5 June June
7 August August
9 October October
11 December December
person swenzel    schedule 29.07.2015
comment
Хорошее решение. На мой взгляд, очень читабельно и легко понять :) Спасибо: D - person elegent; 30.07.2015

Вы можете использовать itertools.islice() + enumerate() для выбора элементов с исходными индексами. :

>>> import calendar
>>> from itertools import islice
>>> for i, month in islice(enumerate(calendar.month_abbr), 6, None, 2):
...     print(i, month)
... 
6 Jun
8 Aug
10 Oct
12 Dec

Он не дублирует информацию о срезе и использует существующие функции со знакомым поведением.

Или, если вы знаете, что исходный итерируемый объект мал; вы можете позвонить list():

>>> list(enumerate(calendar.month_abbr))[6::2]
[(6, 'Jun'), (8, 'Aug'), (10, 'Oct'), (12, 'Dec')]
person jfs    schedule 30.07.2015

Мне не нравится использовать range(len(..., но, может быть, здесь все в порядке.

>>> months = ["January", "February", "March", "April", "May", "June",
...           "July", "August", "September", "October", "November", "December"]
>>> print(*('{:>2} {}'.format(i, months[i]) for i in range(len(months))[5::2]), sep='\n')
 5 June
 7 August
 9 October
11 December

Чтобы расширить однострочный:

offset = 5
step = 2
r = range(len(months))

for i in r[offset::step]:
    print('{:>2} {}'.format(i, months[i]))
person TigerhawkT3    schedule 29.07.2015
comment
Спасибо за ваш ответ :) Но вопрос в том, будет ли ваше решение более читабельным, чем, например, генератор... - person elegent; 30.07.2015
comment
Вероятно, это было бы более читабельно с некоторыми присваиваниями и обычным циклом, а не свернутым в одну строку, но я думаю, что основная концепция в порядке. - person TigerhawkT3; 30.07.2015
comment
Да, вы правы :) Вы можете добавить обычный цикл (цикл и оператор печати разделены) к своему ответу, если хотите: D - person elegent; 30.07.2015
comment
@elegent - Хорошая идея; добавлен. - person TigerhawkT3; 30.07.2015

import itertools as it

months = [
    "January", "February", "March", "April", "May", "June", "July",
    "August", "September", "October", "November", "December"
]

print list(
    it.islice(it.izip(it.count(1), months), 5, len(months), 2)
)

Чтобы ответить на дополнительные вопросы в комментарии:

  • Обычный слайс не будет работать на itertools.izip, так как он не поддерживает метод __getitem__.

  • Да, itertools.izip был удален из Python 3, а обычный встроенный zip работает с той же семантикой генератора.

person Dacav    schedule 29.07.2015
comment
Спасибо :) Но почему мы должны использовать islice форму itertools вместо обычного среза? И насколько я знаю, izip это zip в Python 3.x. Вы добавляете эту информацию в свой ответ. - person elegent; 30.07.2015