[python] How to get first element in a list of tuples?

I have a list like below where the first element is the id and the other is a string:

[(1, u'abc'), (2, u'def')]

I want to create a list of ids only from this list of tuples as below:

[1,2]

I'll use this list in __in so it needs to be a list of integer values.

This question is related to python list

The answer is


>>> a = [(1, u'abc'), (2, u'def')]
>>> [i[0] for i in a]
[1, 2]

if the tuples are unique then this can work

>>> a = [(1, u'abc'), (2, u'def')]
>>> a
[(1, u'abc'), (2, u'def')]
>>> dict(a).keys()
[1, 2]
>>> dict(a).values()
[u'abc', u'def']
>>> 

you can unpack your tuples and get only the first element using a list comprehension:

l = [(1, u'abc'), (2, u'def')]
[f for f, *_ in l]

output:

[1, 2]

this will work no matter how many elements you have in a tuple:

l = [(1, u'abc'), (2, u'def', 2, 4, 5, 6, 7)]
[f for f, *_ in l]

output:

[1, 2]

Those are tuples, not sets. You can do this:

l1 = [(1, u'abc'), (2, u'def')]
l2 = [(tup[0],) for tup in l1]
l2
>>> [(1,), (2,)]

I was thinking that it might be useful to compare the runtimes of the different approaches so I made a benchmark (using simple_benchmark library)

I) Benchmark having tuples with 2 elements enter image description here

As you may expect to select the first element from tuples by index 0 shows to be the fastest solution very close to the unpacking solution by expecting exactly 2 values

import operator
import random

from simple_benchmark import BenchmarkBuilder

b = BenchmarkBuilder()



@b.add_function()
def rakesh_by_index(l):
    return [i[0] for i in l]


@b.add_function()
def wayneSan_zip(l):
    return list(list(zip(*l))[0])


@b.add_function()
def bcattle_itemgetter(l):
     return list(map(operator.itemgetter(0), l))


@b.add_function()
def ssoler_upacking(l):
    return [idx for idx, val in l]

@b.add_function()
def kederrack_unpacking(l):
    return [f for f, *_ in l]



@b.add_arguments('Number of tuples')
def argument_provider():
    for exp in range(2, 21):
        size = 2**exp
        yield size, [(random.choice(range(100)), random.choice(range(100))) for _ in range(size)]


r = b.run()
r.plot()

II) Benchmark having tuples with 2 or more elements enter image description here

import operator
import random

from simple_benchmark import BenchmarkBuilder

b = BenchmarkBuilder()

@b.add_function()
def kederrack_unpacking(l):
    return [f for f, *_ in l]


@b.add_function()
def rakesh_by_index(l):
    return [i[0] for i in l]


@b.add_function()
def wayneSan_zip(l):
    return list(list(zip(*l))[0])


@b.add_function()
def bcattle_itemgetter(l):
     return list(map(operator.itemgetter(0), l))


@b.add_arguments('Number of tuples')
def argument_provider():
    for exp in range(2, 21):
        size = 2**exp
        yield size, [tuple(random.choice(range(100)) for _
                     in range(random.choice(range(2, 100)))) for _ in range(size)]

from pylab import rcParams
rcParams['figure.figsize'] = 12, 7

r = b.run()
r.plot()

This is what operator.itemgetter is for.

>>> a = [(1, u'abc'), (2, u'def')]
>>> import operator
>>> b = map(operator.itemgetter(0), a)
>>> b
[1, 2]

The itemgetter statement returns a function that returns the index of the element you specify. It's exactly the same as writing

>>> b = map(lambda x: x[0], a)

But I find that itemgetter is a clearer and more explicit.

This is handy for making compact sort statements. For example,

>>> c = sorted(a, key=operator.itemgetter(0), reverse=True)
>>> c
[(2, u'def'), (1, u'abc')]

Use the zip function to decouple elements:

>>> inpt = [(1, u'abc'), (2, u'def')]
>>> unzipped = zip(*inpt)
>>> print unzipped
[(1, 2), (u'abc', u'def')]
>>> print list(unzipped[0])
[1, 2]

Edit (@BradSolomon): The above works for Python 2.x, where zip returns a list.

In Python 3.x, zip returns an iterator and the following is equivalent to the above:

>>> print(list(list(zip(*inpt))[0]))
[1, 2]

You can use "tuple unpacking":

>>> my_list = [(1, 'abc'), (2, 'def')]
>>> my_ids = [idx for idx, val in my_list]
>>> my_ids
[1, 2]

At iteration time each tuple is unpacked and its values are set to the variables idx and val.

>>> x = (1, 'abc')
>>> idx, val = x
>>> idx
1
>>> val
'abc'

do you mean something like this?

new_list = [ seq[0] for seq in yourlist ]

What you actually have is a list of tuple objects, not a list of sets (as your original question implied). If it is actually a list of sets, then there is no first element because sets have no order.

Here I've created a flat list because generally that seems more useful than creating a list of 1 element tuples. However, you can easily create a list of 1 element tuples by just replacing seq[0] with (seq[0],).


From a performance point of view, in python3.X

  • [i[0] for i in a] and list(zip(*a))[0] are equivalent
  • they are faster than list(map(operator.itemgetter(0), a))

Code

import timeit


iterations = 100000
init_time = timeit.timeit('''a = [(i, u'abc') for i in range(1000)]''', number=iterations)/iterations
print(timeit.timeit('''a = [(i, u'abc') for i in range(1000)]\nb = [i[0] for i in a]''', number=iterations)/iterations - init_time)
print(timeit.timeit('''a = [(i, u'abc') for i in range(1000)]\nb = list(zip(*a))[0]''', number=iterations)/iterations - init_time)

output

3.491014136001468e-05

3.422205176000717e-05


I wondered why nobody suggested to use numpy, but now after checking i understand. It is maybe not the best for mixed type arrays.

This would be a solution in numpy:

>>> import numpy as np

>>> a = np.asarray([(1, u'abc'), (2, u'def')])
>>> a[:, 0].astype(int).tolist()
[1, 2]

when I ran (as suggested above):

>>> a = [(1, u'abc'), (2, u'def')]
>>> import operator
>>> b = map(operator.itemgetter(0), a)
>>> b

instead of returning:

[1, 2]

I received this as the return:

<map at 0xb387eb8>

I found I had to use list():

>>> b = list(map(operator.itemgetter(0), a))

to successfully return a list using this suggestion. That said, I'm happy with this solution, thanks. (tested/run using Spyder, iPython console, Python v3.6)