reversed(enumerate(x))

Chris Angelico rosuav at gmail.com
Wed Jul 20 15:47:46 EDT 2016


On Thu, Jul 21, 2016 at 5:13 AM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Thu, 21 Jul 2016 03:46 am, Chris Angelico wrote:
>
>> On Thu, Jul 21, 2016 at 3:42 AM, Ian Kelly <ian.g.kelly at gmail.com> wrote:
>>> I had occasion to write something like this:
>>>
>>>     for i, n in reversed(enumerate(x)): pass
>>>
>>> Of course this fails with "TypeError: argument to reversed() must be a
>>> sequence". I ended up using this instead:
>>>
>>>     for i, n in zip(reversed(range(len(x))), reversed(x)): pass
>>
>> At the cost of coalescing the enumeration, you could:
>>
>> for i, n in reversed(list(enumerate(x))): pass
>>
>> It's reasonably clean but less efficient.
>
>
> Less efficient than what?

Than something that let you "enumerate the reverse" of something. Consider:

# Here's what we want to happen.
x = ["foo", "bar", "spam"]
for i, n in reversed(enumerate(x)): # ENABLE MINDREADING MAGIC
    print(i, n)

2 spam
1 bar
0 foo

So we could do this by first reversing the sequence, and then
*enumerating down*. But enumerate() doesn't allow that - it has a
'start' parameter, but no 'step'. Let's pretend it did.

for i, n in enumerate(reversed(x), len(x)-1, -1):
    # same result

The very cleanest, IMO, would be something like this:

def enumerate_reversed(seq):
    n = len(seq)
    for obj in reversed(seq):
        n -= 1
        yield n, obj

> reversed() only operates on sequences, so it can't operate on arbitrary
> iterators of unknown length? Possibly of indefinite length?

Of course not; but it doesn't necessarily require that the sequence
exist as a list, much less as two. My simple and naive example will
take any sequence, pair values with their indices, coalesce the result
as a list, and then iterate backward through that list. Imagine if the
sequence in question were range(1<<256), for instance. You can't
list(enumerate(x)) that, but you can certainly reversed() it and get
its length... well, okay, apparently len(range(1<<256)) is illegal in
CPython, but len(range(1<<63-1)) is fine. You shouldn't need to list()
that.

So yes, my naive version is less memory efficient for large lists.

> Personally, I think your version is the most straightforward and obvious
> solution that works on anything. (Well, perhaps not on infinite iterators.)
> Yes, you have to convert x into a list, but that's in general the only way
> to use reversed() anyway. If your needs are not too great, or simplicity of
> code is more important than

I agree, which is why I suggested it.

> The "best" solution might be some more work: you might convert x into a list
> only if it's an iterator, then iterate over it in reverse:
>
>
> def sequencefy(x):
>     if x is iter(x):
>         return list(x)
>     return x
>
> def enumerate_down(it):
>     seq = sequencefy(it)
>     n = len(seq) - 1
>     for item in reversed(seq):
>         yield (n, item)
>         n -= 1
>
> for i, item = enumerate_down(x):
>     ...
>
>
>
> An advantage of this is that it works well with lazy sequences like
> (x)range. There's no need to build up a huge list of (index, item) pairs
> before reversing it.

Yeah, or the simpler version that I used. All depends how much
complexity you really need.

ChrisA



More information about the Python-list mailing list