4

I run into the case of using an enumerator on selected elements form an iterable (i.e. a sequence or an iterator or similar) and want that the original indices were being returned instead of the default count, starting by 0 and going up to len(iterable) - 1.

A very naive approach would be the declaration of a new generator object called _enumerate()

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

... a new list object months.

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

Using Pythons build-in enumerate function would yield this output for a [5::2] slice:

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


    0 June
    1 August
    2 October
    3 December

The expected output of our own enumerator _enumerate again for a [5::2] slice:

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


    5 June
    7 August
    9 October
    11 December

Do you know any better, more pythonic and more readable solutions? :)

elegent
  • 3,857
  • 3
  • 23
  • 36
  • 2
    Your answer is pythonic and readable enough. – LittleQ Jul 29 '15 at 07:45
  • How about `i*step+offset`? Should also give you the desired indices... – swenzel Jul 29 '15 at 07:47
  • 1
    You could avoid repeating yourself by having `_enumerate` do the slicing as well (ie call it as `_enumerate(month, offset=5, step=2`), but that would require `months` to be a list in order to be efficient. – skyking Jul 29 '15 at 07:49
  • @swenzel: Where do you insert `index * step + offset`? – elegent Jul 29 '15 at 07:55
  • @skyking: Yes. Thanks, thats a good point :) – elegent Jul 29 '15 at 07:57
  • 1
    Wherever you need your "real" index. E.g. `print(index*2+5, element)` with the normal enumerate will also give you "5 June", "7 August"... Or you could put `index = index*step+offset` as first line in your loop. – swenzel Jul 29 '15 at 08:02
  • @swenzel: Oh yeah :) Yes I am silly. Thats a nice solution! Thanks :) – elegent Jul 29 '15 at 08:07

4 Answers4

6

Here my comment as an answer ;)

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])

Prints:

5 June June
7 August August
9 October October
11 December December
swenzel
  • 6,745
  • 3
  • 23
  • 37
4

You could use itertools.islice() + enumerate(), to select elements with original indices:

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

It does not duplicate the slice information and uses existing functions with familiar behavior.

Or if you know that the original iterable is small; you could call list():

>>> list(enumerate(calendar.month_abbr))[6::2]
[(6, 'Jun'), (8, 'Aug'), (10, 'Oct'), (12, 'Dec')]
jfs
  • 399,953
  • 195
  • 994
  • 1,670
1

I don't like using range(len(..., but maybe it's okay here.

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

To expand out of the one-liner:

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

for i in r[offset::step]:
    print('{:>2} {}'.format(i, months[i]))
TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
  • Thanks for your answer :) But it's the question if your solution is more readable than a generator for instance ... – elegent Jul 30 '15 at 07:43
  • It would probably be more readable with some assignments and a normal loop instead of being rolled into a one-liner, but I think the basic concept is okay. – TigerhawkT3 Jul 30 '15 at 07:49
  • Yes, you're right :) You could add the "normal loop" (loop and print statement separated) to your answer if you feel like it :D – elegent Jul 30 '15 at 07:54
  • @elegent - Good idea; added. – TigerhawkT3 Jul 30 '15 at 08:09
1
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)
)

To answer the further questions in comment:

  • Normal slice won't work on itertools.izip, since it does not support the __getitem__ method.

  • Yes, itertools.izip was removed from Python 3, and the regular builtin zip works with the same generator semantics.

Dacav
  • 13,590
  • 11
  • 60
  • 87
  • Thanks :) But why should we use `islice` form `itertools` instead of a "normal" slice? And as far as I know `izip` is `zip` in Python 3.x. You cloud add this information to your answer. – elegent Jul 30 '15 at 08:01