27

I'm looking for a way to reverse a generator object. I know how to reverse sequences:

foo = imap(seq.__getitem__, xrange(len(seq)-1, -1, -1))

But is something similar possible with a generator as the input and a reversed generator as the output (len(seq) stays the same, so the value from the original sequence can be used)?

ak.
  • 2,422
  • 4
  • 21
  • 18
  • 4
    I have to take exception with your example of reversing a sequence. Why not just use `reversed`? or `.reverse`? Even `seq[::-1]` is clearer than what you wrote. – Steven Kryskalla Oct 13 '09 at 16:18
  • Because all these examples will create a new list. My example above is the only way I know to create a list without copying it first. – ak. Oct 13 '09 at 16:42
  • 1
    Well, I learned something new - seq[::-1] *does* in fact create a new list. See my generator expression answer for an alternative using negative indices. – PaulMcG Oct 13 '09 at 17:41
  • 2
    ak - the reversed() function does not make a copy of the sequence, it works very similarly to your example. But as several people have mentioned, there is no way to do this on a generator without first copying the generator to a list. – Matt Good Oct 14 '09 at 04:19

4 Answers4

36

You cannot reverse a generator in any generic way except by casting it to a sequence and creating an iterator from that. Later terms of a generator cannot necessarily be known until the earlier ones have been calculated.

Even worse, you can't know if your generator will ever hit a StopIteration exception until you hit it, so there's no way to know what there will even be a first term in your sequence.

The best you could do would be to write a reversed_iterator function:

def reversed_iterator(iter):
    return reversed(list(iter))

EDIT: You could also, of course, replace reversed in this with your imap based iterative version, to save one list creation.

jcdyer
  • 18,616
  • 5
  • 42
  • 49
  • 18
    Strictly speaking, `list(iter)` is not a *cast*, it is the construction of a list by consuming the iterator iter. I'm not sure Python has any *cast* functions, at least not the in the sense that that term is used in languages like C or Java. int("100") is not a cast, and float(100) is not a cast - both are constructors that return objects. In C, if something is cast, the original object remains the same. If you did such a thing in Python, you could take the id of the original value, and the casted value, and they would be the same. In short: in Python, the cast is died. – PaulMcG Oct 13 '09 at 16:28
  • 2
    @Paul McGuire: I agree with the important parts of what you said. One nit, though: in C, a cast *can* change the original object. If you cast 100 to float, C will change it to 100.0f; C will not simply throw the bit pattern 0x00000064 in to the float variable and let it turn into whatever value that would be in a float. In C, a cast can simply change type (change `int *` to `long int *`, or change `int` to `long int`) or can change value and type. C++ has several casting operators, including one that "reinterprets" type without changing the value at all. – steveha Oct 13 '09 at 18:31
  • I agree with all this. My excperience with C is limited to running through a couple exercises in K&R, so I used the term cast loosely. I'd edit, but that would just cause confusion at this point. So instead, I'll give you an upvote. – jcdyer Oct 13 '09 at 19:08
  • @steveha: Oh, sorry, you are correct in saying casting an int as float will do implicit promotion to float. I was thinking of this: `int* intptr = new int(100); float* fltptr = (float*)intptr; printf("%e", *fltptr); /* no conversion of the actual value 100, so instead we get 1.401298e-043 */` – PaulMcG Oct 13 '09 at 19:29
  • 2
    @PaulMcGuire, I may misunderstand what you mean, but it is the case that in *both* a C cast and in a Python object-construction that the original value remains unchanged. Using `float(i)` in a C expression leaves `i` precisely unchanged, just as it leaves the original `i` object intact in Python. And the newly built object in Python will most certainly *not* have the same ID as the original value! Which can easily be demonstrated with: `i = 1; float(i) is i` → `False`. – Brandon Rhodes Mar 14 '14 at 22:12
  • In a C expression, one would cast using `(float)i`, not `float(i)`. Thanks @steveha for the clarification on cast vs. reinterpret_cast. – PaulMcG Mar 17 '14 at 04:25
  • reversed(iter) also works. Intermediate list like in reversed(list(iter)) is not required. – jsa Sep 27 '19 at 10:37
6

reversed(list(input_generator)) is probably the easiest way.

There's no way to get a generator's values in "reverse" order without gathering all of them into a sequence first, because generating the second item could very well rely on the first having been generated.

Steve Losh
  • 19,642
  • 2
  • 51
  • 44
4

You have to walk through the generator anyway to get the first item so you might as well make a list. Try

reversed(list(g))

where g is a generator.

reversed(tuple(g))

would work as well (I didn't check to see if there is a significant difference in performance).

Kathy Van Stone
  • 25,531
  • 3
  • 32
  • 40
-2
def reverseGenerator(gen):
     new = [i for i in gen]
     yield new[::-1][0]
     new.pop()
     yield from reverseGenerator(i for i in new)
PLINKO
  • 1
  • 1
  • 1
    Isn't there a termination condition missing in your recursion (if `new` is empty)? Otherwise `list(reverseGenerator(range(1,7)))` for example give me an `IndexError: list index out of range`. – fcdt Aug 29 '20 at 10:42
  • 4
    Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Mark Rotteveel Aug 29 '20 at 13:09