Python: Gibt den Index des ersten Elements einer Liste zurück, wodurch eine übergebene Funktion wahr wird

71

Die list.index(x)Funktion gibt den Index in der Liste des ersten Elements zurück, dessen Wert ist x.

Gibt es eine Funktion, list_func_index()ähnlich der index()Funktion, die eine Funktion hat f(), als Parameter? Die Funktion f()wird für jedes Element eder Liste ausgeführt, bis sie f(e)zurückgegeben wird True. Dann list_func_index()gibt den Index von e.

Codeweise:

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

Gibt es eine elegantere Lösung? (und ein besserer Name für die Funktion)

Bandana
quelle

Antworten:

108

Sie können dies in einem Einzeiler mit Generatoren tun:

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

Das Schöne an Generatoren ist, dass sie nur bis zur gewünschten Menge berechnen. Das Anfordern der ersten beiden Indizes ist also (fast) genauso einfach:

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

Erwarten Sie jedoch eine StopIteration-Ausnahme nach dem letzten Index (so funktionieren Generatoren). Dies ist auch in Ihrem "Take-First" -Ansatz praktisch, um zu wissen, dass kein solcher Wert gefunden wurde - die Funktion list.index () würde hier ValueError auslösen.

Paul
quelle
13
Dies ist nicht verschleiert - oder zumindest nicht verschleierter als die Verwendung map(f, seq)anstelle von [f(x) for x in seq]ist. Mit anderen Worten, es ist idiomatisch. Und wie bei anderen Redewendungen ist es nicht einfach, bis es Teil Ihres Wortschatzes ist.
Robert Rossney
2
Nur eine Erinnerung, um zu fangen, StopIterationwenn die Endbedingung möglicherweise nicht erfüllt ist.
Payala
3
Kleiner Hinweis: nextAkzeptiert ein zweites Argument, das im Falle einer Nichtübereinstimmung zurückgegeben wird, anstatt zu erhöhen StopIteration.
Bgusach
15

Eine Möglichkeit ist die integrierte Aufzählungsfunktion :

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

Es ist typisch, eine Funktion wie die, die Sie als "Prädikat" beschreiben, zu bezeichnen. es gibt für eine Frage wahr oder falsch zurück. Deshalb nenne ich es predin meinem Beispiel.

Ich denke auch, dass es besser wäre, zurückzukehren None, da dies die eigentliche Antwort auf die Frage ist. Der Anrufer kann Nonebei Bedarf wählen , ob er explodieren möchte.

Jonathan Feinberg
quelle
1
eleganter, besser benannt, in der Tat
Bandana
Ich denke, das OP wollte das Verhalten des Index beim Erhöhen von ValueError emulieren, wenn der angegebene Wert nicht gefunden wird.
PaulMcG
+1 für die Aufzählung, die ein großer Favorit von mir ist. Ich kann mich nicht erinnern, wann ich das letzte Mal eine Indexvariable auf die altmodische C-Art in Python pflegen musste.
Mattias Nilsson
13

@ Pauls akzeptierte Antwort ist am besten, aber hier ist eine kleine Variante des Querdenkens, hauptsächlich zu Vergnügungs- und Unterrichtszwecken ...:

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

Im Wesentlichen besteht Xder Zweck darin, die Bedeutung von "Gleichheit" von der normalen in "erfüllt dieses Prädikat" zu ändern, wodurch die Verwendung von Prädikaten in allen Arten von Situationen ermöglicht wird, die als Überprüfung auf Gleichheit definiert sind - zum Beispiel würde dies der Fall sein Sie können auch anstelle des if any(is_odd(x) for x in l):kürzeren if X(is_odd) in l:und so weiter codieren .

Lohnt es sich zu benutzen? Nicht, wenn ein expliziterer Ansatz wie der von @Paul genauso praktisch ist (insbesondere, wenn die neue, glänzende integrierte nextFunktion anstelle der älteren, weniger geeigneten .nextMethode verwendet wird, wie ich in einem Kommentar zu dieser Antwort vorschlage). Es gibt jedoch auch andere Situationen, in denen dies (oder andere Varianten der Idee "die Bedeutung von Gleichheit optimieren" und möglicherweise andere Komparatoren und / oder Hashing) angemessen sein können. Meistens wissenswert über die Idee, um zu vermeiden, dass man sie eines Tages von Grund auf neu erfinden muss ;-).

Alex Martelli
quelle
Schön! Aber wie würden wir X "nennen"? So etwas wie "Schlüssel" vielleicht? Weil es mich an l.sort erinnert (key = fn).
Paul
Man könnte es fast "Equals" nennen, also lautet die Zeile l.index (Equals (is_odd))
tgray
3
Ich denke, dass das, was Alex (implizit) vorgeschlagen hat Satisfies, ein guter Name dafür ist.
Robert Rossney
@ Robert, ich mag Satisfies!
Alex Martelli
Es tut mir leid, dicht zu sein, aber wie drücke ich Satisfies in einem Generator aus und verwende sie, der alle seltsamen Elemente der Listenreferenz erzeugt? (Ich glaube, Generatoren haben noch nicht den Dreh raus ...) 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
hinter dem Fall
4

Keine einzige Funktion, aber Sie können es ziemlich einfach tun:

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

Wenn Sie nicht die gesamte Liste auf einmal auswerten möchten, können Sie itertools verwenden, aber es ist nicht so hübsch:

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

Nur die Verwendung eines Generatorausdrucks ist wahrscheinlich besser lesbar als itertoolsallerdings.

Beachten Sie in Python3, mapund filtergeben Sie faule Iteratoren zurück, und Sie können einfach verwenden:

from operator import itemgetter
test = lambda c: c == 'x'
data = ['a', 'b', 'c', 'x', 'y', 'z']
next(filter(itemgetter(1), enumerate(map(test, data))))[0]  # 3
Steve Losh
quelle
2
Leider wertet dies die gesamte Liste aus - wäre schön, eine Lösung zu haben, die Kurzschlüsse, dh sofort zurückgibt, wenn die erste Übereinstimmung gefunden wird.
PaulMcG
2

Eine Variation von Alex 'Antwort. Auf diese Weise müssen XSie nicht jedes Mal eingeben, wenn Sie ein is_oddPrädikat verwenden möchten

>>> class X(object):
...     def __init__(self, pred): self.pred = pred
...     def __eq__(self, other): return self.pred(other)
... 
>>> L = [8,10,4,5,7]
>>> is_odd = X(lambda x: x%2 != 0)
>>> L.index(is_odd)
3
>>> less_than_six = X(lambda x: x<6)
>>> L.index(less_than_six)
2
John La Rooy
quelle
1

Sie könnten dies mit einem Listenverständnis tun:

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

Dann gibt filterl alle Mitglieder der Liste zurück, die den Ausdruck a% 2! = 0 erfüllen. Ich würde eine elegantere Methode sagen ...

Vincent Osinga
quelle
Können Sie Ihre Antwort so bearbeiten, dass sie eher der Funktion des OP ähnelt, die eine Liste enthält und als Parameter fungiert?
Quamrana
6
Das ist falsch. Es wird eine Liste von Werten zurückgegeben, kein einzelner Index.
rekursiv
filterl = [a für a in l wenn is_odd (a)]
Vincent Osinga
1
Ich sagte, dass Sie dies mit einem Listenverständnis tun können und dass es auch eine Liste zurückgibt. Ich wollte nur eine andere Option geben, weil ich nicht sicher bin, was das genaue Problem von Bandana war.
Vincent Osinga