2009-11-09 7 views
30

la funzione list.index(x) restituisce l'indice nell'elenco del primo elemento il cui valore è x.Python: restituisce l'indice del primo elemento di una lista che rende una funzione passata true

c'è una funzione, list_func_index(), simile alla funzione index() che ha una funzione, f(), come parametro. la funzione, f(), viene eseguita su ogni elemento, e, dell'elenco fino a f(e) restituisce True. quindi list_func_index() restituisce l'indice di 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 

c'è una soluzione più elegante? (E un nome migliore per la funzione)

risposta

54

si potrebbe fare in una battuta che utilizzano generatori:

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

La cosa bella di generatori è che si calcolano solo fino all'importo richiesto. Quindi richiedere i primi due indici è (quasi) altrettanto facile:

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

Anche se, si aspettano un'eccezione StopIteration dopo l'ultimo indice (cioè come funzionano i generatori). Questo è anche conveniente nel tuo approccio "take-first", per sapere che non è stato trovato alcun valore --- la funzione list.index() genererebbe ValueError qui.

più Un commento degno di nota da Alex Martelli: In Python 2.6 e fino, next(someiterator) è il modo nuovo e preferito su someiterator.next().

+0

+1 quello che ero nel mezzo della digitazione :-) – bobince

+7

Wow, un'altra vittoria per l'oscurità. Non c'è niente da guadagnare usando un codice "intelligente" come questo. –

+0

@JF: in effetti è quasi una trascrizione letterale della tua soluzione, solo con parentesi anziché "def". anche il valore di ritorno è davanti, invece che alla fine (un vantaggio, direi). e poi c'è anche la possibilità di prendere più risposte, qualcosa che la tua soluzione non può. quindi, qual è esattamente la ragione per cui devi ridurre la risposta del tuo concorrente? – Paul

8

Una possibilità è il built-in funzione di enumerate:

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

E 'tipico per indicare una funzione come quella che lei descrive come un "predicato"; restituisce vero o falso per qualche domanda. Ecco perché lo chiamo pred nel mio esempio.

Penso anche che sarebbe stato meglio restituire None, poiché questa è la vera risposta alla domanda. Il chiamante può scegliere di esplodere su None, se necessario.

+1

più elegante, più di nome, anzi – bandana

+0

credo che il PO ha voluto emulare il comportamento di indice di sollevare ValueError se il valore dato non viene trovato. – PaulMcG

+0

+1 per l'enumerazione che è una delle mie preferite. Non riesco a ricordare l'ultima volta che in realtà ho dovuto mantenere una variabile di indice il vecchio modo C in Python. –

0

si potrebbe fare questo con una lista-di comprensione:

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

Poi filterl torneranno tutti i membri della lista che soddisfano l'espressione a 2% = 0. direi un metodo più elegante ...!

+0

Puoi modificare la tua risposta in modo che sia più simile alla funzione dell'OP che ha un elenco e funge da parametro? – quamrana

+4

Questo è sbagliato. Restituisce un elenco di valori, non un singolo indice. – recursive

+0

filterl = [a per a in l se is_odd (a)] –

3

non una sola funzione, ma si può fare abbastanza facilmente:

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

Se non si desidera valutare l'intero elenco in una sola volta è possibile utilizzare itertools, ma non è così bella:

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

L'uso di un'espressione di generatore è probabilmente più leggibile di itertools.

+0

Sfortunatamente, questo valuta l'intero elenco - sarebbe bello avere una soluzione che cortocircuiti, cioè restituisce immediatamente quando trova la prima corrispondenza. – PaulMcG

+0

Potrebbe essere fatto con una comprensione del generatore. – recursive

9

@ risposta accettato di Paolo è migliore, ma qui è un po 'variante latero-pensiero, per lo più per scopi di divertimento e istruzione ...:

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

sostanza, lo scopo X s' è quello di cambiare il significato di " l'uguaglianza "da quella normale a" soddisfa questo predicato ", consentendo in tal modo l'uso di predicati in tutti i tipi di situazioni che sono definite come controllo dell'uguaglianza - ad esempio, ti consentirebbe anche di codificare, invece di if X(is_odd) in l:if X(is_odd) in l: , e così via.

Vale la pena utilizzare? Non quando un approccio più esplicito come quello preso da @Paul è altrettanto utile (specialmente quando si cambia la funzione nuova, lucida integrata next anziché quella precedente, meno appropriata, .next, come suggerisco in un commento a quella risposta), ma ci sono altre situazioni in cui (o altre varianti dell'idea "modificare il significato di uguaglianza", e forse altri comparatori e/o hashing) possono essere appropriati. Soprattutto, vale la pena conoscere l'idea, per evitare di doverlo inventare da zero un giorno ;-).

+0

Bello! Ma cosa dovremmo "nominare" X? Qualcosa come "Chiave", forse? Perché mi ricorda l.sort (key = fn). – Paul

+0

Si potrebbe quasi chiamarlo "uguale", quindi la riga legge l.index (Equals (is_odd)) – tgray

+1

Penso che ciò che Alex (implicitamente) suggeriva, 'Satisfies', è un buon nome per questo. –

1

Una variazione sulla risposta di Alex. Ciò evita di dover digitare X ogni volta che si desidera utilizzare is_odd o qualsiasi predicato

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