12

So, I have an iterable of 3-tuples, generated lazily. I'm trying to figure out how to turn this into 3 iterables, consisting of the first, second, and third elements of the tuples, respectively. However, I wish this to be done lazily.

So, for example, I wish [(1, 2, 3), (4, 5, 6), (7, 8, 9)] to be turned into [1, 4, 7], [2, 5, 8], [3, 6, 9]. (Except I want iterables not lists.)

The standard zip(*data) idiom doesn't work, because the argument unpacking expands the entire iterable. (You can verify this by noting that zip(*((x, x+1, x+2) for x in itertools.count(step=3))) hangs.)

The best I've come up with thus far is the following:

def transpose(iterable_of_three_tuples):
    teed = itertools.tee(iterable_of_three_tuples, 3)
    return map(lambda e: e[0], teed[0]), map(lambda e: e[1], teed[1]), map(lambda e: e[2], teed[2])

This seems to work. But it hardly seems like clean code. And it does a lot of what seems to be unnecessary work.

TLW
  • 1,373
  • 9
  • 22
  • 1
    `izip` from the `itertools` module? – g.d.d.c Apr 16 '14 at 20:49
  • Since you have working code you may get better results from Code Review – wnnmaw Apr 16 '14 at 20:51
  • How do you expect your new iterator to get, say, the `7` from the third tuple without consuming the entire outer iterable? Your `tranpose` will also hang if you try it on an infinite iterator like `count`. – BrenBarn Apr 16 '14 at 20:54
  • It would help if you include an example of how you intend to use it. perhaps we can propose a better approach – shx2 Apr 16 '14 at 21:04
  • 1
    @wnnmaw No, it is fine to post on SO. He/she is asking for a way to do it, not for us to review and edit his/her code. – Quintec Apr 16 '14 at 21:20
  • (might be a good idea to mention you're using python3) – shx2 Apr 16 '14 at 21:29
  • @g.d.d.c I don't see how izip could help me here. And I'm using Python 3, where zip has the same behaviour as izip anyways. – TLW Apr 16 '14 at 22:18

1 Answers1

3

Your transpose is pretty much exactly what you need.

With any solution you'd choose, you'd have to buffer the unused values (e.g. to get to the 7, you have to read 1-6, and store them in memory for when the other iterables ask for them). tee already does exactly that kind of buffering, so there's no need implementing it yourself.

The only other (minor) thing is that I'd write it slightly differently, avoiding the map and lambdas:

def transpose(iterable_of_three_tuples):
    teed = itertools.tee(iterable_of_three_tuples, 3)
    return ( e[0] for e in teed[0] ),  ( e[1] for e in teed[1] ),  ( e[2] for e in teed[2] )
shx2
  • 61,779
  • 13
  • 130
  • 153