Ryan B. Lynch
Ryan B. Lynch

Reputation: 2347

In Python, how can I find the index of the first item in a list that is NOT some value?

Python's list type has an index(x) method. It takes a single parameter x, and returns the (integer) index of the first item in the list that has the value x.

Basically, I need to invert the index(x) method. I need to get the index of the first value in a list that does NOT have the value x. I would probably be able to even just use a function that returns the index of the first item with a value != None.

I can think of a 'for' loop implementation with an incrementing counter variable, but I feel like I'm missing something. Is there an existing method, or a one-line Python construction that can handle this?

In my program, the situation comes up when I'm handling lists returned from complex regex matches. All but one item in each list have a value of None. If I just needed the matched string, I could use a list comprehension like '[x for x in [my_list] if x is not None]', but I need the index in order to figure out which capture group in my regex actually caused the match.

Upvotes: 16

Views: 18418

Answers (5)

Marcelo Cantos
Marcelo Cantos

Reputation: 185862

[i for i, x in enumerate(my_list) if x != value][0]

If you're not sure whether there's a non-matching item, use this instead:

match = [i for i, x in enumerate(my_list) if x != value]
if match:
    i = match[0]
    # i is your number.

You can make this even more "functional" with itertools, but you will soon reach the point where a simple for loop is better. Even the above solutions aren't as efficient as a for loop, since they construct a list of all non-matching indices before you pull the one of interest.

Upvotes: 1

Alex Martelli
Alex Martelli

Reputation: 881765

Exiting at the first match is really easy: instead of computing a full list comprehension (then tossing away everything except the first item), use next over a genexp. Assuming for example that you want -1 when no item satisfies the condition of being != x,

return next((i for i, v in enumerate(L) if v != x), -1)

This is Python 2.6 syntax; if you're stuck with 2.5 or earlier, .next() is a method of the genexp (or other iterator) and doesn't accept a default value like the -1 above (so if you don't want to see a StopIteration exception you'll have to use a try/except). But then, there is a reason more releases were made after 2.5 -- continuous improvement of the language and its built-ins!-)

Upvotes: 18

tzot
tzot

Reputation: 95961

A silly itertools-based solution:)

import itertools as it, operator as op, functools as ft

def index_ne(item, sequence):
    sequence= iter(sequence)
    counter= it.count(-1) # start counting at -1
    pairs= it.izip(sequence, counter) # pair them
    get_1st= it.imap(op.itemgetter(0), pairs) # drop the used counter value
    ne_scanner= it.ifilter(ft.partial(op.ne, item), get_1st) # get only not-equals
    try:
        ne_scanner.next() # this should be the first not equal
    except StopIteration:
        return None # or raise some exception, all items equal to item
    else:
        return counter.next() # should be the index of the not-equal item

if __name__ == "__main__":
    import random

    test_data= [0]*20
    print "failure", index_ne(0, test_data)

    index= random.randrange(len(test_data))
    test_data[index]= 1
    print "success:", index_ne(0, test_data), "should be", index

All this just to take advantage of the itertools.count counting :)

Upvotes: -1

Stephen
Stephen

Reputation: 49176

Using a list comprehension when you only need the first just feels slimy (to me). Use a for-loop and exit early.

>>> lst = [None, None, None, "foo", None]
>>> for i, item in enumerate(lst):
...   if item: break
... else:
...   print "not found"
... 
>>> i
3

Upvotes: 5

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798746

enumerate() returns an iterator that yields a tuple of the current index of the iterable as well as the item itself.

Upvotes: 5

Related Questions