7

Possible Duplicate:
A Transpose/Unzip Function in Python

I have a list of sequences, each sequence has two items. I would like to turn this into two lists.

catalog = [('abc', '123'), ('foo', '456'), ('bar', '789'), ('test', '1337')]

Right now I'm just doing this:

names = []
vals = []

for product in catalog:
        names.append(product[0])
        vals.append(product[1])

print (names)
print (vals)

Which outputs two lists, and works just fine:

['abc', 'foo', 'bar', 'test']
['123', '456', '789', '1337']

Is there a neater, more 'pythonic' way of doing this? Or should I stick with what I have? Any corrections or feedback on programming style is welcome, I'm new and trying to learn best practices.

Community
  • 1
  • 1
Ryan Rapini
  • 365
  • 1
  • 3
  • 11

2 Answers2

14
>>> catalog = [('abc', '123'), ('foo', '456'), ('bar', '789'), ('test', '1337')]
>>> names, vals = zip(*catalog)
>>> names
('abc', 'foo', 'bar', 'test')
>>> vals
('123', '456', '789', '1337')

The *catalog syntax here is called Unpacking Argument Lists, and zip(*catalog) translates into the call zip(catalog[0], catalog[1], catalog[2], ...).

The zip() builtin function groups iterables by indices, so when you pass a bunch of two-element tuples as above, you get a two-element list of tuples where the first tuple contains the first element of each tuple from catalog, and the second tuple contains the second element from each tuple from catalog.

In a quick timeit test the zip() version outperforms a looping approach when I tested with 1,000,000 pairs:

In [1]: catalog = [(i, i+1) for i in range(1000000)]

In [2]: def with_zip():
   ...:     return zip(*catalog)
   ...: 

In [3]: def without_zip():
   ...:     names, vals = [], []
   ...:     for name, val in catalog:
   ...:         names.append(name)
   ...:         vals.append(val)
   ...:     return names, vals
   ...: 

In [4]: %timeit with_zip()
1 loops, best of 3: 176 ms per loop

In [5]: %timeit without_zip()
1 loops, best of 3: 250 ms per loop
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • This is perfect, thanks! I'd never heard of this function before. I'm linking to the documentation for this function, for future readers. http://docs.python.org/3.3/library/functions.html#zip – Ryan Rapini Dec 17 '12 at 18:56
  • 1
    It's terse and simple, but is it performant? In any other language creating a zillion parameters would be horrible. – Mark Ransom Dec 17 '12 at 19:00
  • @MarkRansom -- I've wondered about that on occasion as well... – mgilson Dec 17 '12 at 19:17
  • Hmm. My results are different -- I get `zip` ~ 490 ms, `without_zip` ~ 326 ms, and a manual `[c[0] for c in catalog]/[c[1] etc.]` only 205. [`itemgetter` seems comparable to the listcomp.] I tend to ingore factors of order unity, though, and so I'd probably use the `zip` myself regardless of speed. – DSM Dec 17 '12 at 19:31
3

Sure, use this:

lst = [('abc', '123'), ('foo', '456'), ('bar', '789'), ('test', '1337')]
tup1, tup2 = zip(*lst)

The above will return a pair of tuples with the elements. If you need lists, then use this:

lst = [('abc', '123'), ('foo', '456'), ('bar', '789'), ('test', '1337')]
lst1, lst2 = map(list, zip(*lst))
Óscar López
  • 232,561
  • 37
  • 312
  • 386