Trova un oggetto nell'elenco con attributo uguale a un valore (che soddisfa qualsiasi condizione)


221

Ho un elenco di oggetti. Voglio trovare un (primo o qualunque cosa) oggetto in questo elenco che abbia un attributo (o risultato del metodo - qualunque cosa) uguale a value.

Qual è il modo migliore per trovarlo?

Ecco il test case:

  class Test:
      def __init__(self, value):
          self.value = value

  import random

  value = 5

  test_list = [Test(random.randint(0,100)) for x in range(1000)]

  # that I would do in Pascal, I don't believe isn't anywhere near 'Pythonic'
  for x in test_list:
      if x.value == value:
          print "i found it!"
          break

Penso che usare i generatori e reduce()non farà alcuna differenza perché continuerebbe a scorrere l'elenco.

ps .: L'equazione a valueè solo un esempio. Ovviamente vogliamo ottenere un elemento che soddisfi qualsiasi condizione.


2
Ecco una buona discussione di questa domanda: tomayko.com/writings/cleanest-python-find-in-list-function
Andrew Hare

Il post originale è ridicolmente obsoleto, ma la seconda risposta corrisponde esattamente alla mia versione di una riga. Non sono convinto che sia meglio della versione di base del loop.
AGF

Risposte:


434
next((x for x in test_list if x.value == value), None)

Ciò ottiene il primo elemento dall'elenco che corrisponde alla condizione e restituisce Nonese nessun elemento corrisponde. È la mia forma preferita di espressione singola.

Però,

for x in test_list:
    if x.value == value:
        print "i found it!"
        break

L'ingenua versione loop-break, è perfettamente Pythonic - è concisa, chiara ed efficiente. Per farlo corrispondere al comportamento del one-liner:

for x in test_list:
    if x.value == value:
        print "i found it!"
        break
else:
    x = None

Questo verrà assegnato Nonea xse non si è breakfuori dal giro.


73
+1 per la rassicurante "L'ingenua versione loop-break, è perfettamente Pythonic".
LaundroMat,

ottima soluzione, ma come posso modificare la tua linea in modo da poter fare in modo che x.value significhi effettivamente x.fieldMemberName dove quel nome è memorizzato in valore? field = "name" successivo ((x per x in test_list se x.field == valore), nessuno) in modo che in questo caso, sto effettivamente controllando x.name, non x.field
Stewart Dale

3
@StewartDale Non è del tutto chiaro ciò che stai chiedendo, ma penso che intendi ... if getattr(x, x.fieldMemberName) == value. Ciò recupererà l'attributo da xcon il nome archiviato fieldMemberNamee lo confronterà value.
AGF

1
@ThatTechGuy - La elseclausola è pensata per essere in forloop, non il if. (Modifica rifiutata).
AGF

1
@agf Wow, non avevo letteralmente idea che esistesse .. book.pythontips.com/en/latest/for_-_else.html fantastico!
ThatTechGuy

25

Dal momento che non è stato menzionato solo per il completamento. Il buon vecchio filtro per filtrare gli elementi da filtrare.

Programmazione funzionale ftw.

####### Set Up #######
class X:

    def __init__(self, val):
        self.val = val

elem = 5

my_unfiltered_list = [X(1), X(2), X(3), X(4), X(5), X(5), X(6)]

####### Set Up #######

### Filter one liner ### filter(lambda x: condition(x), some_list)
my_filter_iter = filter(lambda x: x.val == elem, my_unfiltered_list)
### Returns a flippin' iterator at least in Python 3.5 and that's what I'm on

print(next(my_filter_iter).val)
print(next(my_filter_iter).val)
print(next(my_filter_iter).val)

### [1, 2, 3, 4, 5, 5, 6] Will Return: ###
# 5
# 5
# Traceback (most recent call last):
#   File "C:\Users\mousavin\workspace\Scripts\test.py", line 22, in <module>
#     print(next(my_filter_iter).value)
# StopIteration


# You can do that None stuff or whatever at this point, if you don't like exceptions.

So che generalmente nella comprensione dell'elenco python sono preferite o almeno questo è ciò che leggo, ma non vedo il problema per essere onesti. Naturalmente Python non è un linguaggio FP, ma Map / Reduce / Filter sono perfettamente leggibili e sono i casi d'uso standard più standard nella programmazione funzionale.

Quindi eccoti. Conosci la tua programmazione funzionale.

elenco delle condizioni del filtro

Non sarà più facile di così:

next(filter(lambda x: x.val == value,  my_unfiltered_list)) # Optionally: next(..., None) or some other default value to prevent Exceptions

Mi piace molto lo stile di questo, ma ci sono due potenziali problemi. 1 : funziona solo in Python 3; in Python 2, filterrestituisce un elenco con cui non è compatibile next. 2 : richiede una corrispondenza definita, altrimenti otterrai StopIterationun'eccezione.
Freethebees,

1
1: Non sono a conoscenza di Python 2. Quando ho iniziato a usare Python, Python 3 era già disponibile. Sfortunatamente non ho idea delle specifiche di Python 2. 2. @freethebees come sottolineato da agf. Puoi usare il prossimo (..., Nessuno) o qualche altro valore predefinito, se non sei un fan delle eccezioni. L'ho anche aggiunto come commento al mio codice.
Nima Mousavi,

@freethebees Point 2 potrebbe effettivamente essere buono. Quando ho bisogno di un determinato oggetto in un elenco, fallire velocemente è una buona cosa.
Kap il

7

Un semplice esempio : abbiamo il seguente array

li = [{"id":1,"name":"ronaldo"},{"id":2,"name":"messi"}]

Ora, vogliamo trovare l'oggetto nell'array con ID uguale a 1

  1. Utilizzare il metodo nextcon comprensione dell'elenco
next(x for x in li if x["id"] == 1 )
  1. Utilizzare la comprensione dell'elenco e restituire il primo elemento
[x for x in li if x["id"] == 1 ][0]
  1. Funzione personalizzata
def find(arr , id):
    for x in arr:
        if x["id"] == id:
            return x
find(li , 1)

Uscita tutti i metodi sopra è {'id': 1, 'name': 'ronaldo'}


1

Mi sono appena imbattuto in un problema simile e ho escogitato una piccola ottimizzazione per il caso in cui nessun oggetto nell'elenco soddisfa i requisiti (per il mio caso d'uso ciò ha comportato un notevole miglioramento delle prestazioni):

Insieme all'elenco test_list, tengo un set aggiuntivo test_value_set che consiste in valori dell'elenco su cui devo filtrare. Quindi qui l'altra parte della soluzione di agf diventa molto veloce.


1

Potresti fare qualcosa del genere

dict = [{
   "id": 1,
   "name": "Doom Hammer"
 },
 {
    "id": 2,
    "name": "Rings ov Saturn"
 }
]

for x in dict:
  if x["id"] == 2:
    print(x["name"])

Questo è quello che uso per trovare gli oggetti in una lunga serie di oggetti.


In che modo è diverso allora quale interrogatore ha già provato?
Anum Sheraz,

Volevo mostrare come può ottenere l'oggetto e la matrice di oggetti nel modo più semplice.
Illud,

0

È inoltre possibile implementare un confronto avanzato tramite il __eq__metodo per la propria Testclasse e utilizzare l' inoperatore. Non sono sicuro se questo è il miglior modo autonomo, ma nel caso in cui sia necessario confrontare le Testistanze in base ad valuealtrove, questo potrebbe essere utile.

class Test:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        """To implement 'in' operator"""
        # Comparing with int (assuming "value" is int)
        if isinstance(other, int):
            return self.value == other
        # Comparing with another Test object
        elif isinstance(other, Test):
            return self.value == other.value

import random

value = 5

test_list = [Test(random.randint(0,100)) for x in range(1000)]

if value in test_list:
    print "i found it"

0

Per il codice sottostante, xGen è un'espressione generatrice anonoma, yFilt è un oggetto filtro. Si noti che per xGen viene restituito il parametro Nessuno aggiuntivo anziché generare StopIteration quando l'elenco è esaurito.

arr =((10,0), (11,1), (12,2), (13,2), (14,3))

value = 2
xGen = (x for x in arr if x[1] == value)
yFilt = filter(lambda x: x[1] == value, arr)
print(type(xGen))
print(type(yFilt))

for i in range(1,4):
    print('xGen: pass=',i,' result=',next(xGen,None))
    print('yFilt: pass=',i,' result=',next(yFilt))

Produzione:

<class 'generator'>
<class 'filter'>
xGen: pass= 1  result= (12, 2)
yFilt: pass= 1  result= (12, 2)
xGen: pass= 2  result= (13, 2)
yFilt: pass= 2  result= (13, 2)
xGen: pass= 3  result= None
Traceback (most recent call last):
  File "test.py", line 12, in <module>
    print('yFilt: pass=',i,' result=',next(yFilt))
StopIteration
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.