Znajdź obiekt na liście, który ma atrybut równy pewnej wartości (spełniający dowolny warunek)

221

Mam listę obiektów. Chcę znaleźć jeden (pierwszy lub dowolny) obiekt na tej liście, który ma atrybut (lub wynik metody - cokolwiek) równy value.

Jak najlepiej to znaleźć?

Oto przypadek testowy:

  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

Myślę, że korzystanie z generatorów reduce()nie robi żadnej różnicy, ponieważ wciąż będzie iterować po liście.

ps .: Równanie do valuejest tylko przykładem. Oczywiście chcemy uzyskać element spełniający dowolny warunek.

seler
źródło
2
Oto dobra dyskusja na to pytanie: tomayko.com/writings/cleanest-python-find-in-list-function
Andrew Hare
Oryginalny post jest absurdalnie nieaktualny, ale druga odpowiedź dokładnie pasuje do mojej wersji jednowierszowej. Nie jestem jednak przekonany, że jest to lepsze niż podstawowa wersja pętli.
agf

Odpowiedzi:

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

Pobiera pierwszy element z listy, który pasuje do warunku, i zwraca, Nonejeśli żaden element nie pasuje. To moja preferowana forma pojedynczego wyrażenia.

Jednak,

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

Naiwna wersja z pętlą, jest doskonale Pythonic - jest zwięzła, przejrzysta i wydajna. Aby dopasować go do zachowania jednowarstwowego:

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

Przydzieli Noneto, xjeśli nie wyjdziesz breakz pętli.

agf
źródło
72
+1 za uspokajające „Naiwna wersja z zapętleniem pętli jest idealnie Pythoniczna”.
LaundroMat,
świetne rozwiązanie, ale jak mogę zmodyfikować twoją linię, aby można było sprawić, że x.value faktycznie oznacza x.fieldMemberName, gdzie ta nazwa jest przechowywana w wartości? pole = "nazwa" dalej ((x dla x na liście testowej, jeśli x.field == wartość), Brak), więc w tym przypadku faktycznie sprawdzam pod kątem x.nazwa, a nie x.fielda
Stewart Dale
3
@StewartDale Nie jest całkowicie jasne, o co pytasz, ale myślę, że masz na myśli ... if getattr(x, x.fieldMemberName) == value. Spowoduje to pobranie atrybutu xz zachowaną nazwą fieldMemberNamei porównanie go z value.
agf
1
@ThatTechGuy - elseKlauzula ma być w forpętli, a nie if. (Odrzucona edycja).
agf
1
@agf Wow dosłownie nie miałem pojęcia, że ​​istnieje .. book.pythontips.com/en/latest/for_-_else.html super!
ThatTechGuy
25

Ponieważ nie wspomniano o tym tylko dla uzupełnienia. Dobry stary filtr do filtrowania twoich elementów.

Programowanie funkcjonalne 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.

Wiem, że ogólnie w liście pythonów preferowane są wyrozumiałości, a przynajmniej tak czytam, ale nie uważam tego za szczery. Oczywiście Python nie jest językiem FP, ale Map / Reduce / Filter są doskonale czytelne i są najbardziej standardowymi standardowymi przypadkami użycia w programowaniu funkcjonalnym.

Więc proszę bardzo. Poznaj swoje programowanie funkcjonalne.

lista warunków filtrów

Nie będzie łatwiejsze niż to:

next(filter(lambda x: x.val == value,  my_unfiltered_list)) # Optionally: next(..., None) or some other default value to prevent Exceptions
Nima Mousavi
źródło
Styl mi się podoba, ale są dwa potencjalne problemy. 1 : Działa tylko w Pythonie 3; w Pythonie 2 filterzwraca listę niezgodną z next. 2 : wymaga określonego dopasowania, w przeciwnym razie otrzymasz StopIterationwyjątek.
freethebees
1
1: Nie znam Python 2. Kiedy zacząłem używać Python, Python 3 był już dostępny. Niestety nie mam pojęcia o szczegółach Python 2. 2. @ frerehebees, jak wskazał agf. Możesz użyć następnej (..., Brak) lub innej wartości domyślnej, jeśli nie jesteś fanem wyjątków. Dodałem go również jako komentarz do mojego kodu.
Nima Mousavi
@ Freethebees Point 2 może być naprawdę dobry. Kiedy potrzebuję określonego obiektu na liście, szybkie zawodzenie jest dobrą rzeczą.
kap
7

Prosty przykład : mamy następującą tablicę

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

Teraz chcemy znaleźć obiekt w tablicy o identyfikatorze równym 1

  1. Użyj metody nextze zrozumieniem listy
next(x for x in li if x["id"] == 1 )
  1. Skorzystaj ze zrozumienia listy i zwróć pierwszą pozycję
[x for x in li if x["id"] == 1 ][0]
  1. Funkcja niestandardowa
def find(arr , id):
    for x in arr:
        if x["id"] == id:
            return x
find(li , 1)

Dane wyjściowe wszystkich powyższych metod to {'id': 1, 'name': 'ronaldo'}

Mohammad Nazari
źródło
1

Właśnie natknąłem się na podobny problem i opracowałem małą optymalizację dla przypadku, w którym żaden obiekt na liście nie spełnia wymagań (w moim przypadku użycia spowodowało to znaczną poprawę wydajności):

Wraz z listą test_list przechowuję dodatkowy zestaw test_value_set, który składa się z wartości listy, na których muszę filtrować. Więc tutaj inna część rozwiązania agf staje się bardzo szybka.

użytkownik1578297
źródło
1

Możesz zrobić coś takiego

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

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

Tego właśnie używam do znajdowania obiektów w długim szeregu obiektów.

Illud
źródło
Czym to się różni od tego, który pytający już próbował?
Anum Sheraz
Chciałem pokazać, w jaki sposób może on uzyskać obiekt i szereg obiektów w najprostszy sposób.
Illud
0

Możesz także zaimplementować bogate porównanie __eq__metodami dla swojej Testklasy i inoperatorem. Nie jestem pewien, czy jest to najlepszy samodzielny sposób, ale w przypadku, gdy trzeba porównać Testinstancje oparte na valueinnym miejscu, może to być przydatne.

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"
tm-
źródło
0

Dla kodu poniżej xGen jest anonimowym wyrażeniem generatora, yFilt jest obiektem filtru. Zauważ, że dla xGen zwracany jest dodatkowy parametr None zamiast rzucania StopIteration po wyczerpaniu listy.

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

Wynik:

<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
edW
źródło