Using Python's list index() method on a list of tuples or objects?
Asked Answered
P

13

72

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.

Pruitt answered 3/6, 2009 at 20:1 Comment(0)
W
83

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.

Wenger answered 3/6, 2009 at 20:7 Comment(8)
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.Cressy
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.Newsman
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!Helbonna
Can you throw Triptych's in that test? :)Wenger
Sure... and we have a winner. I used Triptych's second (super performant) example that returns as soon as it finds the result as his first example was essentially the same as mine, but with an extra function all. The super-performant version is indeed the fastest.Helbonna
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.Helbonna
Naturally. Thanks Jarret. I wish I could upvote you more than once. :)Wenger
This is pretty much exactly what I wanted to do. Thanks!Pruitt
N
30

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
Newsman answered 3/6, 2009 at 20:54 Comment(2)
+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.Wenger
Thanks. Normally I would use the collect() version - looks so much nicer.Newsman
H
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]
Helbonna answered 3/6, 2009 at 20:12 Comment(4)
That's neat. I wonder if this or the list comprehension is faster? Either way, +1.Wenger
The problem with this is that you are iterating twice to get the index.Dominant
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.Helbonna
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.Helbonna
G
9

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
Gale answered 3/6, 2009 at 20:50 Comment(0)
P
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
Pydna answered 17/2, 2016 at 1:49 Comment(0)
E
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
Earth answered 27/7, 2011 at 20:2 Comment(0)
L
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
Lactation answered 19/6, 2012 at 17:38 Comment(0)
I
1
z = list(zip(*tuple_list))
z[1][z[0].index('persimon')]
Immobilize answered 16/1, 2013 at 17:19 Comment(0)
G
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)
Globuliferous answered 16/10, 2012 at 16:44 Comment(0)
E
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)).

Endosteum answered 4/5, 2016 at 8:21 Comment(1)
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?Mckinney
M
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]
Mandell answered 9/6, 2017 at 1:54 Comment(2)
Hello, and welcome to StackOverflow. Please add some explanation to your answer.Injector
This is accidentally quadratic. Instead, you should use enumerate: [idx for idx, t in enumerate(tuple_list) if t[1] == 7].Castro
P
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.

Pastoralize answered 23/7, 2018 at 1:45 Comment(0)
A
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
Ashwell answered 3/6, 2022 at 18:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.