views:

189

answers:

5

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.

+3  A: 

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

Ignacio Vazquez-Abrams
Aw, I really was missing something. Yeah, the exact usage is something like this: [i for i, x in enumerate(my_list) if x is not None]. Thanks!
Ryan B. Lynch
A: 
[i for i,x in 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 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.

Marcelo Cantos
+3  A: 

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
Stephen
Did you read the 3rd paragraph in my original question? The for loop implementation is trivial. I was specifically looking for a one-liner or an existing method. (Based on the votes, though, it seems like you weren't the only person that missed the point. I should have made it a little more obvious.)
Ryan B. Lynch
@Ryan B. Lynch: I did read that. You mentioned you could do it using a for-loop and a counter... which is more code and a little ugly. I used enumerate(), to get the counter automatically. From your question, I wasn't sure whether you knew enumerate existed. OTOH, I hadn't known next() existed (but I lasted used python 2.5), so I learned something from Alex's solution. I dunno why people voted for this, maybe the votes came from trying to do something in one-line that can trivially and transparently be done in two?
Stephen
Good point about the counter vs break thing. As for opinions about "trying to do something in one-line that can trivially and transparently be done in two", your original answer conveyed it pretty clearly. But if coding style considerations are motivating your response, why make it an answer? Isn't that what comments are for?
Ryan B. Lynch
@Ryan B. Lynch: Coding style didn't motivate my response - motivation 1: using a list comprehension (building a list of all results) and returning the first is wasteful. motivation 2: enumerate() helps do what you want. ok motivation 3: it's really obvious in a for-loop :) As it stands it is a possible solution to your question, so I think it reasonably belongs in an answer. I'm sorry it isn't the answer you were looking for, and I'm glad Alex provided that. :)
Stephen
Have you read the meta thread (http://meta.stackoverflow.com/questions/18552/answer-whats-asked-or-answer-whats-desired) about this? You have a perfectly good answer, but it's not an answer to my question, here.
Ryan B. Lynch
I hadn't read that, but from that thread it seems a lot of people add "but I suggest you do...". So your question asked "is there an existing python method or one-line". I could've responded with "use enumerate" like the other guy and avoided this discussion, but I figured an illustration would help. While I'm at it, I'll show 'for/else' to show not found.
Stephen
By the way, your question clearly states "In Python, how can I find the index of the first item in a list that is NOT some value?"... This _is exactly_ an answer to that. One nice thing about SO, is that a bunch of people get to offer suggested ones and you get to pick your favorite. That's exactly how this transpired.
Stephen
A: 

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 :)

ΤΖΩΤΖΙΟΥ
Note that on some occasions, `item.__ne__` could be used instead of the `ft.partial(op.ne, item)` device above; however, the latter works with all `item`s.
ΤΖΩΤΖΙΟΥ
This is impressively incomprehensible.
Glenn Maynard
+3  A: 

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!-)

Alex Martelli
I actually don't need to handle the "no item satisfies" condition, since the regex method always returns at least one non-None list item. So I think this should work: next(i for i, x in enumerate(my_list, ) if x is not None). Under the debugger, it seems to stop at the first match, so I'm sold. Cool trick.
Ryan B. Lynch
@Ryan, yes, if you don't need a default result when all the items are None, your simpler expression will work fine.
Alex Martelli