views:

596

answers:

5

the list.index(x) function returns the index in the list of the first item whose value is x.

is there a function, list_func_index(), similar to the index() function that has a function, f(), as a parameter. the function, f() is run on every element, e, of the list until f(e) returns True. then list_func_index() returns the index of e.

codewise:

>>> def list_func_index(lst, func):
      for i in range(len(lst)):
        if func(lst[i]):
          return i
      raise ValueError('no element making func True')

>>> l = [8,10,4,5,7]
>>> def is_odd(x): return x % 2 != 0
>>> list_func_index(l,is_odd)
3

is there a more elegant solution? (and a better name for the function)

+6  A: 

One possibility is the built-in enumerate function:

def index_of_first(lst, pred):
    for i,v in enumerate(lst):
        if pred(v):
            return i
    return None

It's typical to refer a function like the one you describe as a "predicate"; it returns true or false for some question. That's why I call it pred in my example.

I also think it would be better form to return None, since that's the real answer to the question. The caller can choose to explode on None, if required.

Jonathan Feinberg
more elegant, better named, indeed
bandana
I think the OP wanted to emulate index's behavior of raising ValueError if the given value is not found.
Paul McGuire
+1 for enumerate which is a big favorite of mine. I can't remember the last time I actually had to maintain an index variable the old-fashioned C way in python.
Mattias Nilsson
A: 

you could do this with a list-comprehension:

l = [8,10,4,5,7]
filterl = [a for a in l if a % 2 != 0]

Then filterl will return all members of the list fulfilling the expression a % 2 != 0. I would say a more elegant method...

Vincent Osinga
Can you edit your answer to be more like the OP's function that has a list and function as parameters?
quamrana
This is wrong. It returns a list of values, not a single index.
recursive
filterl = [a for a in l if is_odd(a)]
Vincent Osinga
I said that you can do this with a list comprehension and also that it returns a list. Just wanted to give a different option, because I'm not sure what the exact problem of bandana was.
Vincent Osinga
+1  A: 

Not one single function, but you can do it pretty easily:

>>> test = lambda c: c == 'x'
>>> data = ['a', 'b', 'c', 'x', 'y', 'z', 'x']
>>> map(test, data).index(True)
3
>>>

If you don't want to evaluate the entire list at once you can use itertools, but it's not as pretty:

>>> from itertools import imap, ifilter
>>> from operator import itemgetter
>>> test = lambda c: c == 'x'
>>> data = ['a', 'b', 'c', 'x', 'y', 'z']
>>> ifilter(itemgetter(1), enumerate(imap(test, data))).next()[0]
3
>>>

Just using a generator expression is probably more readable than itertools though.

Steve Losh
Unfortunately, this evaluates the entire list - would be nice to have a solution that short-circuits, that is, returns immediately when finding the first match.
Paul McGuire
It could be done with a generator comprehension.
recursive
+13  A: 

You could do that in a one-liner using generators:

(i for i,v in enumerate(l) if is_odd(v)).next()

The nice thing about generators is that they only compute up to the requested amount. So requesting the first two indices is (almost) just as easy:

y = (i for i,v in enumerate(l) if is_odd(v))
x1 = y.next()
x2 = y.next()

Though, expect a StopIteration exception after the last index (that is how generators work). This is also convenient in your "take-first" approach, to know that no such value was found --- the list.index() function would throw ValueError here.

extra A comment worth mentioning by Alex Martelli: In Python 2.6 and up, next(someiterator) is the new and preferred way over someiterator.next().

Paul
+1 what I was in the middle of typing :-)
bobince
Wow, another victory for obscurity. There's nothing to be gained from using "clever" code like this.
Jonathan Feinberg
@JF: in fact it is almost a literate transcription of your solution, only with braces instead of "def". also the return value is in front, instead of at the end (an advantage, I'd say). and then there is even the option of taking multiple answers, something your solution can't. so what is exactly the reason for you to downvote your competitor's answer?
Paul
+1 succinct and clear.
Rod Hyde
I didn't downvote it because it's "competitive"; I downvoted it because it's obfuscatory. FWIW, I often upvote my fellow answerers.
Jonathan Feinberg
Will anybody ever start using the right `2.6`/`3.*` idiom, that is, `next(i for i, v in enumerate(L) if pred(v))`?! It's shorter AND you can add a 2nd argument to get in lieu of a StopIteration. Ah well, +1 anyway since it's obviously more elegant and idiomatic than the coded-out loop even in this imperfect form.
Alex Martelli
This isn't obfuscatory - or at least, it's not any more obfuscatory than using `map(f, seq)` instead of `[f(x) for x in seq]` is. In other words, it's idiomatic. And like other idioms, it's not straightforward until it's part of your vocabulary.
Robert Rossney
+4  A: 

@Paul's accepted answer is best, but here's a little lateral-thinking variant, mostly for amusement and instruction purposes...:

>>> class X(object):
...   def __init__(self, pred): self.pred = pred
...   def __eq__(self, other): return self.pred(other)
... 
>>> l = [8,10,4,5,7]
>>> def is_odd(x): return x % 2 != 0
... 
>>> l.index(X(is_odd))
3

essentially, X's purpose is to change the meaning of "equality" from the normal one to "satisfies this predicate", thereby allowing the use of predicates in all kinds of situations that are defined as checking for equality -- for example, it would also let you code, instead of if any(is_odd(x) for x in l):, the shorter if X(is_odd) in l:, and so forth.

Worth using? Not when a more explicit approach like that taken by @Paul is just as handy (especially when changed to use the new, shiny built-in next function rather than the older, less appropriate .next method, as I suggest in a comment to that answer), but there are other situations where it (or other variants of the idea "tweak the meaning of equality", and maybe other comparators and/or hashing) may be appropriate. Mostly, worth knowing about the idea, to avoid having to invent it from scratch one day;-).

Alex Martelli
Nice one! But what would we "name" X? Something like "Key" perhaps? Because it reminds me of l.sort(key=fn).
Paul
You could almost call it "Equals", so the line reads l.index(Equals(is_odd))
tgray
I think that what Alex (implicitly) suggested, `Satisfies`, is a good name for it.
Robert Rossney
@Robert, I do like Satisfies!
Alex Martelli
Sorry to be dense, but how do I express and use Satisfies in a generator that produces all the odd elements of list ref? (Haven't gotten the hang of generators yet, I guess ...)ref = [8,10,4,5,7]def is_odd(x): return x % 2 != 0class Satisfies(object): def __init__(self, pred): self.pred = pred def __eq__(self, test_this): return self.pred(test_this)print ref.index( Satisfies(is_odd))#>>>3
behindthefall
Ouch! That was supposed to be a code snippet! (Does this work?)ref = [8,10,4,5,7] def is_odd(x): return x % 2 != 0 class Satisfies(object): def __init__(self, pred): self.pred = pred def __eq__(self, test_this): return self.pred(test_this) print ref.index( Satisfies(is_odd)) #>>>3
behindthefall
BTW, next() is very appealing, but Cygwin and Pydee both are laggards and haven't advanced to 2.6 yet, AFAIK, so .next() will probably keep showing up for a while.
behindthefall
@behindthefall, give up on putting code in comments -- you've gotta post or edit an existing answer or question (and make sure to whine on the meta site, the more of us explaining this limit on comments is crazy the likelier Joel will ever heed us;-). Anyway, unless you're using s/thing with built-in checks for == like index, `if x=Satisfied(p)` is just a weird way of sayinf `if p(x)`;-). And yeah, even minor-version upgrades always lag (app engine is 2.5 too), but I've written my own easy `def next` for Py<2.6, post a question if you want me to show it!
Alex Martelli