6

I have a list of tuples, e.g.

>>> l = [ ("a",1), ("b",2), ("c",3) ]

and I can assume that elements are unique. Now I would like to get the first element of that tuple whose second element is 2 (which is 'b' in this example). First attempt is:

>>> [ x for x, y in l if y == 2 ][0]
'b'

This seems somewhat cumbersome considering this creates a second list only to index the 0th element. Another way of doing this is to reverse all tuples in the given list l and build a dictionary, then index that dictionary:

>>> dict([ (y, x) for x, y in l ])[2]
'b'

This seems even more awkward considering the amount of data shuffling involved in reversing the list and creating a dictionary. Lastly, the most verbose but perhaps fastest way to do this is to simply iterate over the list:

>>> def get(l) :
...     for x, y in l :
...         if y == 2 : 
...             return x
...     assert not "Should not happen."
... 
>>> get(l)
'b'

My question is: are there any better and more pythonic ways to searching through this list?

Jens
  • 8,423
  • 9
  • 58
  • 78

3 Answers3

7

Try this:

next(x for x in l if x[1] == 2)[0]

The advantage of using next() is that we iterate only over the minimum amount of elements required for finding what we're looking for, so no, it's not equivalent to creating a whole new list using a list comprehension and then returning the first element.

Óscar López
  • 232,561
  • 37
  • 312
  • 386
3

You can also use next():

In [1]: l = [("a", 1), ("b", 2), ("c", 3)]

In [2]: next(a for a, b in l if b == 2)
Out[2]: 'b'

Note that it would throw StopIteration exception if nothing found unless a default is supplied:

In [3]: next(a for a, b in l if b == 100)
---------------------------------------------------------------------------
StopIteration                             
Traceback (most recent call last)
<ipython-input-38-14fe91d87aab> in <module>()
----> 1 next(a for a, b in l if b == 100)

StopIteration: 

In [4]: next((a for a, b in l if b == 100), 0)
Out[4]: 0
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • This is synonymous with the `[0]` index into the first list comprehension, so not *really* much different than what I've got? – Jens Apr 03 '15 at 02:35
  • 1
    @Jens Oscar has just provided the explanation, check it out. I think you should accept his answer. – alecxe Apr 03 '15 at 02:37
1

It depends on how to wish to pay, space or time. Your could not have both.

1 If we want to speed up:

l = [ ("a",1), ("b",2), ("c",3) ]
_dict = {k:v for v,k in l}
print(_dict.get(2, None))

2 If space is limited, try the other answer's next or your loop.

flycee
  • 11,948
  • 3
  • 19
  • 14
  • I don't follow 1: to build the dictionary it takes a full iteration over the list, followed by a dictionary lookup. Moreover, using the `get()` method is the most expensive of the dictionary lookups (see [this thread](http://stackoverflow.com/questions/9358983/python-dictionary-and-default-values#17501506)). So — how does 1 speed up over using `next()`? – Jens Apr 03 '15 at 15:51
  • The reason why it's slow in topic of the link is the data so small, and then the cost is the function call. But if the data or tuples is million or large, the feature of hash map or set will speed up like O(1), not like the other methods O(n) (e.g. next(x for x in l if x[1] == 2)[0]) – flycee Apr 03 '15 at 16:11
  • Agreed, if you stabilize with the lookup and not the construction. Unfortunately, in my case, I need to iterate over ever-changing lists of tuples. – Jens Apr 03 '15 at 16:13