14

I am wondering if there is a better way to iterate two items at a time in a list. I work with Maya a lot, and one of its commands (listConnections) returns a list of alternating values. The list will look like [connectionDestination, connectionSource, connectionDestination, connectionSource]. To do anything with this list, I would ideally like to do something similar to:

for destination, source in cmds.listConnections():
    print source, destination

You could, of course just iterate every other item in the list using [::2] and enumerate and source would be the index+1, but then you have to add in extra checks for odd numbered lists and stuff.

The closest thing I have come up with so far is:

from itertools import izip
connections = cmds.listConnections()
for destination, source in izip(connections[::2], connections[1::2]):
    print source, destination

This isn't super important, as I already have ways of doing what I want. This just seems like one of those things that there should be a better way of doing it.

Mathieson
  • 1,194
  • 2
  • 13
  • 28
  • 1
    take a look at this post, maybe it'll help http://stackoverflow.com/questions/1624883/alternative-way-to-split-a-list-into-groups-of-n – Hans Z Jul 05 '12 at 18:31
  • also, what you have seems like a good way to do it. does it work? – Hans Z Jul 05 '12 at 18:32

2 Answers2

11

You can use the following method for grouping items from an iterable, taken from the documentation for zip():

connections = cmds.listConnections()
for destination, source in zip(*[iter(connections)]*2):
    print source, destination

Or for a more readable version, use the grouper recipe from the itertools documentation:

def grouper(n, iterable, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • Oh, wow. It took me a moment to figure out how `grouper()` works. We build the `args` list so that all the args are references to a single iterator; thus, when `itertools.izip_longest()` tries to pull a value from each sequence, it is really pulling the next value from the same iterator! Elegant and efficient! – steveha Jul 05 '12 at 18:55
2

All, Great question & answer. I'd like to provide another solution that should compliment Andrew Clark's answer (props for using itertools!). His answer returns each value once like this:

iterable = [0, 1, 2, 3, 4, 5, 6,...] n = 2 grouper(n, iterable, fillvalue=None) --> [(0, 1), (2, 3), (3, 4), (5, 6),...]

In the code below each value will appear in n sub-sequences. Like this:

def moving_window(n, iterable):
  start, stop = 0, n
  while stop <= len(iterable):
      yield iterable[start:stop]
      start += 1
      stop += 1

--> [(0, 1), (1, 2), (2, 3), (3, 4),...]

A common application for this type of 'moving window' or 'kernel' is moving averages in the sciences and finance.

Also note that the yield statement allows each sub-sequence to be created as it's needed and not stored in memory.

John C. King
  • 121
  • 1
  • 6