74

Python's list type has an index() method that takes one parameter and returns the index of the first item in the list matching the parameter. For instance:

>>> some_list = ["apple", "pear", "banana", "grape"] >>> some_list.index("pear") 1 >>> some_list.index("grape") 3 

Is there a graceful (idiomatic) way to extend this to lists of complex objects, like tuples? Ideally, I'd like to be able to do something like this:

>>> tuple_list = [("pineapple", 5), ("cherry", 7), ("kumquat", 3), ("plum", 11)] >>> some_list.getIndexOfTuple(1, 7) 1 >>> some_list.getIndexOfTuple(0, "kumquat") 2 

getIndexOfTuple() is just a hypothetical method that accepts a sub-index and a value, and then returns the index of the list item with the given value at that sub-index. I hope

Is there some way to achieve that general result, using list comprehensions or lambas or something "in-line" like that? I think I could write my own class and method, but I don't want to reinvent the wheel if Python already has a way to do it.

13 Answers 13

85

How about this?

>>> tuple_list = [("pineapple", 5), ("cherry", 7), ("kumquat", 3), ("plum", 11)] >>> [x for x, y in enumerate(tuple_list) if y[1] == 7] [1] >>> [x for x, y in enumerate(tuple_list) if y[0] == 'kumquat'] [2] 

As pointed out in the comments, this would get all matches. To just get the first one, you can do:

>>> [y[0] for y in tuple_list].index('kumquat') 2 

There is a good discussion in the comments as to the speed difference between all the solutions posted. I may be a little biased but I would personally stick to a one-liner as the speed we're talking about is pretty insignificant versus creating functions and importing modules for this problem, but if you are planning on doing this to a very large amount of elements you might want to look at the other answers provided, as they are faster than what I provided.

Sign up to request clarification or add additional context in comments.

8 Comments

Nice solution, but will not produce the desired result: it will not return the index of the first item only, but will iterate over the whole list and return all matches.
Still creates a new list of size N in memory, which isn't necessary. Also runs in O(n) average case, which can be improved to O(n/2). Yes I know that's still O(n) technically.
The issue van raise is easily resolved by just picking the first result ([0]) from the list of multiple matches. Interestingly, if I run the same speed test as I did in the comments to my answer with a) Paolo's original enumerate comphrehension, b) Paolo's revised comprehension and index, and c) the map/operator/index approach from my answer, option C is the when there's more than one match in tuple_list (ie: more than one "kumquat"). B is next best. A is slowest. This is fun!
Can you throw Triptych's in that test? :)
Though I should repeat that this test is obviously a quick one-off, done under conditions that may not mirror someone's production need, and is hardly well-planned, so take it with a truck-load of salt.
|
32

Those list comprehensions are messy after a while.

I like this Pythonic approach:

from operator import itemgetter tuple_list = [("pineapple", 5), ("cherry", 7), ("kumquat", 3), ("plum", 11)] def collect(l, index): return map(itemgetter(index), l) # And now you can write this: collect(tuple_list,0).index("cherry") # = 1 collect(tuple_list,1).index("3") # = 2 

If you need your code to be all super performant:

# Stops iterating through the list as soon as it finds the value def getIndexOfTuple(l, index, value): for pos,t in enumerate(l): if t[index] == value: return pos # Matches behavior of list.index raise ValueError("list.index(x): x not in list") getIndexOfTuple(tuple_list, 0, "cherry") # = 1 

2 Comments

+1 as the super performant is indeed the fastest solution posted. I would personally still stick to the one liner as the speed difference at this level is pretty meaningless but it's good to know anyways.
Thanks. Normally I would use the collect() version - looks so much nicer.
11

One possibility is to use the itemgetter function from the operator module:

import operator f = operator.itemgetter(0) print map(f, tuple_list).index("cherry") # yields 1 

The call to itemgetter returns a function that will do the equivalent of foo[0] for anything passed to it. Using map, you then apply that function to each tuple, extracting the info into a new list, on which you then call index as normal.

map(f, tuple_list) 

is equivalent to:

[f(tuple_list[0]), f(tuple_list[1]), ...etc] 

which in turn is equivalent to:

[tuple_list[0][0], tuple_list[1][0], tuple_list[2][0]] 

which gives:

["pineapple", "cherry", ...etc] 

4 Comments

That's neat. I wonder if this or the list comprehension is faster? Either way, +1.
The problem with this is that you are iterating twice to get the index.
Paolo asks an interesting question... as I think everyone suspects, the list comprehension and enumerate approach is slightly faster... over 100000 runs on my ever-so-scientific test, the enumerate approach was about 10milliseconds faster.
I think Paolo and I should blend answers :-) After he edited his answer, I re-ran the speed tests for cases where there are more than one match in the tuple_list... and the operator approach is fastest... see my comment in Paolo's answer.
10

You can do this with a list comprehension and index()

tuple_list = [("pineapple", 5), ("cherry", 7), ("kumquat", 3), ("plum", 11)] [x[0] for x in tuple_list].index("kumquat") 2 [x[1] for x in tuple_list].index(7) 1 

Comments

7

Inspired by this question, I found this quite elegant:

>>> tuple_list = [("pineapple", 5), ("cherry", 7), ("kumquat", 3), ("plum", 11)] >>> next(i for i, t in enumerate(tuple_list) if t[1] == 7) 1 >>> next(i for i, t in enumerate(tuple_list) if t[0] == "kumquat") 2 

Comments

2

I would place this as a comment to Triptych, but I can't comment yet due to lack of rating:

Using the enumerator method to match on sub-indices in a list of tuples. e.g.

li = [(1,2,3,4), (11,22,33,44), (111,222,333,444), ('a','b','c','d'), ('aa','bb','cc','dd'), ('aaa','bbb','ccc','ddd')] # want pos of item having [22,44] in positions 1 and 3: def getIndexOfTupleWithIndices(li, indices, vals): # if index is a tuple of subindices to match against: for pos,k in enumerate(li): match = True for i in indices: if k[i] != vals[i]: match = False break; if (match): return pos # Matches behavior of list.index raise ValueError("list.index(x): x not in list") idx = [1,3] vals = [22,44] print getIndexOfTupleWithIndices(li,idx,vals) # = 1 idx = [0,1] vals = ['a','b'] print getIndexOfTupleWithIndices(li,idx,vals) # = 3 idx = [2,1] vals = ['cc','bb'] print getIndexOfTupleWithIndices(li,idx,vals) # = 4 

Comments

1

ok, it might be a mistake in vals(j), the correction is:

def getIndex(li,indices,vals): for pos,k in enumerate(lista): match = True for i in indices: if k[i] != vals[indices.index(i)]: match = False break if(match): return pos 

Comments

1
z = list(zip(*tuple_list)) z[1][z[0].index('persimon')] 

Comments

0
tuple_list = [("pineapple", 5), ("cherry", 7), ("kumquat", 3), ("plum", 11)] def eachtuple(tupple, pos1, val): for e in tupple: if e == val: return True for e in tuple_list: if eachtuple(e, 1, 7) is True: print tuple_list.index(e) for e in tuple_list: if eachtuple(e, 0, "kumquat") is True: print tuple_list.index(e) 

Comments

0

Python's list.index(x) returns index of the first occurrence of x in the list. So we can pass objects returned by list compression to get their index.

>>> tuple_list = [("pineapple", 5), ("cherry", 7), ("kumquat", 3), ("plum", 11)] >>> [tuple_list.index(t) for t in tuple_list if t[1] == 7] [1] >>> [tuple_list.index(t) for t in tuple_list if t[0] == 'kumquat'] [2] 

With the same line, we can also get the list of index in case there are multiple matched elements.

>>> tuple_list = [("pineapple", 5), ("cherry", 7), ("kumquat", 3), ("plum", 11), ("banana", 7)] >>> [tuple_list.index(t) for t in tuple_list if t[1] == 7] [1, 4] 

2 Comments

Hello, and welcome to StackOverflow. Please add some explanation to your answer.
This is accidentally quadratic. Instead, you should use enumerate: [idx for idx, t in enumerate(tuple_list) if t[1] == 7].
0

I guess the following is not the best way to do it (speed and elegance concerns) but well, it could help :

from collections import OrderedDict as od t = [('pineapple', 5), ('cherry', 7), ('kumquat', 3), ('plum', 11)] list(od(t).keys()).index('kumquat') 2 list(od(t).values()).index(7) 7 # bonus : od(t)['kumquat'] 3 

list of tuples with 2 members can be converted to ordered dict directly, data structures are actually the same, so we can use dict method on the fly.

Comments

0

This is also possible using Lambda expressions:

l = [('rana', 1, 1), ('pato', 1, 1), ('perro', 1, 1)] map(lambda x:x[0], l).index("pato") # returns 1 
Edit to add examples:
l=[['rana', 1, 1], ['pato', 2, 1], ['perro', 1, 1], ['pato', 2, 2], ['pato', 2, 2]] 

extract all items by condition:

filter(lambda x:x[0]=="pato", l) #[['pato', 2, 1], ['pato', 2, 2], ['pato', 2, 2]] 

extract all items by condition with index:

>>> filter(lambda x:x[1][0]=="pato", enumerate(l)) [(1, ['pato', 2, 1]), (3, ['pato', 2, 2]), (4, ['pato', 2, 2])] >>> map(lambda x:x[1],_) [['pato', 2, 1], ['pato', 2, 2], ['pato', 2, 2]] 

Note: The _ variable only works in the interactive interpreter. More generally, one must explicitly assign _, i.e. _=filter(lambda x:x[1][0]=="pato", enumerate(l)).

3 Comments

I think this solution (map(lambda x:x[0], l).index("pato")) is actually one of the better ones, but I suspect the author does not speak English. Would anyone be willing to re-write this? Alternatively, is it acceptable in this community to wholly rewrite an answer from scratch if its author does not speak English?
The solution using map have to be corrected for Python 3, so for the variable "tuple_list" at the assignment, the proper answer for the index of kumquat-tuple would be tuple(map(lambda x:x[0], tuple_list)).index("kumquat") but I would prefere another solution, tuple(zip(*tuple_list))[0].index("kumquat") which I personally feel more pythonic.
nods in agreement. I've taken to the [*zip(tuple_list)][0].index('foo') flavor for casting generators to indexable lists in Python3.
0

I came up with a quick and dirty approach using max and lambda.

>>> tuple_list = [("pineapple", 5), ("cherry", 7), ("kumquat", 3), ("plum", 11)] >>> target = 7 >>> max(range(len(tuple_list)), key=lambda i: tuple_list[i][1] == target) 1 

There is a caveat though that if the list does not contain the target, the returned index will be 0, which could be misleading.

>>> target = -1 >>> max(range(len(tuple_list)), key=lambda i: tuple_list[i][1] == target) 0 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.