Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

reversed(enumerate(x))

59 views
Skip to first unread message

Ian Kelly

unread,
Jul 20, 2016, 1:42:59 PM7/20/16
to
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

This works but is extraordinarily ugly and not terribly clear. I think
it's less confusing however than:

for i, n in zip(range(len(x)-1, -1, -1), reversed(n)): pass

How would you write this?

Chris Angelico

unread,
Jul 20, 2016, 1:47:13 PM7/20/16
to
On Thu, Jul 21, 2016 at 3:42 AM, Ian Kelly <ian.g...@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.

ChrisA

Brendan Abel

unread,
Jul 20, 2016, 1:54:35 PM7/20/16
to
You could create your own generator that wraps enumerate

def reverse_enumerate(iterable):
for i, val in enumerate(reversed(iterable)):
yield len(iterable) - 1 - i, val

for i, val in reverse_enumerate(x):
...

On Wed, Jul 20, 2016 at 10:42 AM, Ian Kelly <ian.g...@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
>
> This works but is extraordinarily ugly and not terribly clear. I think
> it's less confusing however than:
>
> for i, n in zip(range(len(x)-1, -1, -1), reversed(n)): pass
>
> How would you write this?
> --
> https://mail.python.org/mailman/listinfo/python-list
>

Steven D'Aprano

unread,
Jul 20, 2016, 3:13:34 PM7/20/16
to
Less efficient than what?

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

def iter():
while random.random() > 0.01:
yield random.random()

So in the most general case, you have to form a list before you can reverse
it.

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

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.


--
Steven
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.

Random832

unread,
Jul 20, 2016, 3:16:28 PM7/20/16
to
On Wed, Jul 20, 2016, at 13:42, Ian Kelly wrote:
> I had occasion to write something like this:
>
> for i, n in reversed(enumerate(x)): pass
>
> How would you write this?

I'd write my own version of enumerate with a step argument, and call
enumerate(reversed(x), start=len(x), step=-1)

Chris Angelico

unread,
Jul 20, 2016, 3:48:04 PM7/20/16
to
On Thu, Jul 21, 2016 at 5:13 AM, Steven D'Aprano <st...@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...@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

Michael Selik

unread,
Jul 20, 2016, 9:55:17 PM7/20/16
to

> On Jul 20, 2016, at 12:42 PM, Ian Kelly <ian.g...@gmail.com> wrote:
>
> for i, n in reversed(enumerate(x)): pass
>
> fails with "TypeError: argument to reversed() must be a sequence".

So make it a sequence:
for i, n in reversed(list(enumerate(x))): pass

If ``x`` is very large, you can use your zip/range technique to save memory.

Ian Kelly

unread,
Jul 21, 2016, 12:14:51 PM7/21/16
to
On Wed, Jul 20, 2016 at 1:16 PM, Random832 <rand...@fastmail.com> wrote:
> On Wed, Jul 20, 2016, at 13:42, Ian Kelly wrote:
>> I had occasion to write something like this:
>>
>> for i, n in reversed(enumerate(x)): pass
>>
>> How would you write this?
>
> I'd write my own version of enumerate with a step argument, and call
> enumerate(reversed(x), start=len(x), step=-1)

Makes it a little too easy to commit an off-by-one error, I think. As
you did here.

Ian Kelly

unread,
Jul 21, 2016, 12:26:48 PM7/21/16
to
On Wed, Jul 20, 2016 at 11:54 AM, Brendan Abel <007br...@gmail.com> wrote:
> You could create your own generator that wraps enumerate
>
> def reverse_enumerate(iterable):
> for i, val in enumerate(reversed(iterable)):
> yield len(iterable) - 1 - i, val
>
> for i, val in reverse_enumerate(x):
> ...

Thanks, I like this. But I think I'll define it as:

def reverse_enumerate(sequence):
return zip(reversed(range(len(sequence))), reversed(sequence))

To avoid performing the loop in Python.
0 new messages