147

I'm trying to obtain the n-th elements from a list of tuples.

I have something like:

elements = [(1,1,1),(2,3,7),(3,5,10)]

I wish to extract only the second elements of each tuple into a list:

seconds = [1, 3, 5]

I know that it could be done with a for loop but I wanted to know if there's another way since I have thousands of tuples.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
pleasedontbelong
  • 19,542
  • 12
  • 53
  • 77

8 Answers8

234
n = 1 # N. . .
[x[n] for x in elements]
cs95
  • 379,657
  • 97
  • 704
  • 746
luc
  • 41,928
  • 25
  • 127
  • 172
48

This also works:

zip(*elements)[1]

(I am mainly posting this, to prove to myself that I have groked zip...)

See it in action:

>>> help(zip)

Help on built-in function zip in module builtin:

zip(...)

zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]

Return a list of tuples, where each tuple contains the i-th element from each of the argument sequences. The returned list is truncated in length to the length of the shortest argument sequence.

>>> elements = [(1,1,1),(2,3,7),(3,5,10)]
>>> zip(*elements)
[(1, 2, 3), (1, 3, 5), (1, 7, 10)]
>>> zip(*elements)[1]
(1, 3, 5)
>>>

Neat thing I learned today: Use *list in arguments to create a parameter list for a function...

Note: In Python3, zip returns an iterator, so instead use list(zip(*elements)) to return a list of tuples.

jpp
  • 159,742
  • 34
  • 281
  • 339
Daren Thomas
  • 67,947
  • 40
  • 154
  • 200
  • 3
    and use `**dict` to create keyword arguments: `def test(foo=3, bar=3): return foo*bar` then `d = {'bar': 9, 'foo'=12}; print test(**d)` – Wayne Werner Jul 22 '10 at 12:58
  • @Wayne Werner: Yep. This stuff was all just passive knowledge (I don't often use it) - but it's good to be reminded now and then so you know where / what to look for... – Daren Thomas Jul 22 '10 at 13:14
  • 4
    True story - I find that in anything I use often enough (Python, vim), I tend to need reminders of neat/cool features that I've forgotten because I don't use them *that* often. – Wayne Werner Jul 22 '10 at 14:26
  • the *list syntax is pretty useful. any idea where this is described in the official python documentation? – user1748155 Oct 10 '13 at 20:50
  • I only found it in the tutorial: http://docs.python.org/2/tutorial/controlflow.html#unpacking-argument-lists – Daren Thomas Oct 11 '13 at 08:00
  • 1
    This is very neat! – dhdhagar Feb 28 '21 at 04:37
  • 5
    No longer works, probably this got lost in the transition form Python 2 to 3: "TypeError: 'zip' object is not subscriptable." The zip function does not return a list, but a "zip" object now. – dsteinhoefel May 24 '22 at 07:52
31

I know that it could be done with a FOR but I wanted to know if there's another way

There is another way. You can also do it with map and itemgetter:

>>> from operator import itemgetter
>>> map(itemgetter(1), elements)

This still performs a loop internally though and it is slightly slower than the list comprehension:

setup = 'elements = [(1,1,1) for _ in range(100000)];from operator import itemgetter'
method1 = '[x[1] for x in elements]'
method2 = 'map(itemgetter(1), elements)'

import timeit
t = timeit.Timer(method1, setup)
print('Method 1: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup)
print('Method 2: ' + str(t.timeit(100)))

Results:

Method 1: 1.25699996948
Method 2: 1.46600008011

If you need to iterate over a list then using a for is fine.

Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 2
    A small addition: In python-3.x the benchmark will show that map only takes a fraction of a millisecond. That's because it will return an iterator. method2 = 'list(map(itemgetter(1), elements))' renders the old behavior. – Maik Beckmann May 13 '11 at 11:59
13

Found this as I was searching for which way is fastest to pull the second element of a 2-tuple list. Not what I wanted but ran same test as shown with a 3rd method plus test the zip method

setup = 'elements = [(1,1) for _ in range(100000)];from operator import itemgetter'
method1 = '[x[1] for x in elements]'
method2 = 'map(itemgetter(1), elements)'
method3 = 'dict(elements).values()'
method4 = 'zip(*elements)[1]'

import timeit
t = timeit.Timer(method1, setup)
print('Method 1: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup)
print('Method 2: ' + str(t.timeit(100)))
t = timeit.Timer(method3, setup)
print('Method 3: ' + str(t.timeit(100)))
t = timeit.Timer(method4, setup)
print('Method 4: ' + str(t.timeit(100)))

Method 1: 0.618785858154
Method 2: 0.711684942245
Method 3: 0.298138141632
Method 4: 1.32586884499

So over twice as fast if you have a 2 tuple pair to just convert to a dict and take the values.

Graeme Gellatly
  • 151
  • 1
  • 5
  • This is probably obvious but I would mention `dict(elements).values()` will result in one-element dict as opposed to list comprahension or map. This is exactly what I wanted (I was interested in unique touples) (+1 and big thanks for posting) but others might wonder why dict is faster - it's not allocating memory but only checking against existing element. – Greg0ry Dec 21 '16 at 13:34
7

Timings for Python 3.6 for extracting the second element from a 2-tuple list.

Also, added numpy array method, which is simpler to read (but arguably simpler than the list comprehension).

from operator import itemgetter
elements = [(1,1) for _ in range(100000)]

%timeit second = [x[1] for x in elements]
%timeit second = list(map(itemgetter(1), elements))
%timeit second = dict(elements).values()
%timeit second = list(zip(*elements))[1]
%timeit second = np.array(elements)[:,1]

and the timings:

list comprehension:  4.73 ms ± 206 µs per loop
list(map):           5.3 ms ± 167 µs per loop
dict:                2.25 ms ± 103 µs per loop
list(zip)            5.2 ms ± 252 µs per loop
numpy array:        28.7 ms ± 1.88 ms per loop

Note that map() and zip() do not return a list anymore, hence the explicit conversion.

Oleg
  • 10,406
  • 3
  • 29
  • 57
  • `dict().values()` needs `list` as well. – hpaulj Dec 09 '20 at 21:07
  • @Oleg I don't understand in the 'dict` method how the code understands that we want to look at the second element. Is it the defaults in values == 1 ?. Say, one needs to do the same for the 3 or 10th elements. what changes in the dict method? – msh855 Apr 25 '21 at 14:58
3
map (lambda x:(x[1]),elements)
fedorqui
  • 275,237
  • 103
  • 548
  • 598
Thras
  • 31
  • 3
1

Using islice and chain.from_iterable:

>>> from itertools import chain, islice
>>> elements = [(1,1,1),(2,3,7),(3,5,10)]
>>> list(chain.from_iterable(islice(item, 1, 2) for item in elements))
[1, 3, 5]

This can be useful when you need more than one element:

>>> elements = [(0, 1, 2, 3, 4, 5), 
                (10, 11, 12, 13, 14, 15), 
                (20, 21, 22, 23, 24, 25)]
>>> list(chain.from_iterable(islice(tuple_, 2, 5) for tuple_ in elements))
[2, 3, 4, 12, 13, 14, 22, 23, 24]
Georgy
  • 12,464
  • 7
  • 65
  • 73
1

I liked @daren's answer, but it gives an error that zip is not subscribable since you can't slice the zip iterator. Instead, you want to add the zip into a list to extract the elements you want based on the index.

elements = [(1,1,1),(2,3,7),(3,5,10)]
slices = list(zip(*elements))[1]

output:

(1, 3, 5)
Ahmed
  • 796
  • 1
  • 5
  • 16