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.
>>> 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
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
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 equivalentlist(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)
Source: Stackoverflow.com