Controleer of iets (niet) in een lijst in Python staat

Ik heb een lijst met tuples in Pythonen ik heb een voorwaardelijke waar ik de branch ALLEEN wil nemen als de tuple niet in de lijst staat (als het in de lijst staat, dan wil ik de if branch niet nemen)

if curr_x -1 > 0 and (curr_x-1 , curr_y) not in myList: 
    # Do Something

Dit werkt echter niet echt voor mij. Wat heb ik verkeerd gedaan?


Antwoord 1, autoriteit 100%

De bug zit waarschijnlijk ergens anders in je code, want het zou goed moeten werken:

>>> 3 not in [2, 3, 4]
False
>>> 3 not in [4, 5, 6]
True

Of met tupels:

>>> (2, 3) not in [(2, 3), (5, 6), (9, 1)]
False
>>> (2, 3) not in [(2, 7), (7, 3), "hi"]
True

Antwoord 2, autoriteit 4%

Hoe controleer ik of iets (niet) in een lijst in Python staat?

De goedkoopste en meest leesbare oplossing is het gebruik van de inoperator (of in uw specifieke geval, not in). Zoals vermeld in de documentatie,

De operators inen not intesten op lidmaatschap. x in sevalueert tot
Trueals xlid is van s, en anders False. x not in sretourneert
de ontkenning van x in s.

Bovendien,

De operator not inis gedefinieerd om de inverse ware waarde van inte hebben.

y not in xis logischerwijs hetzelfde als not y in x.

Hier zijn een paar voorbeelden:

'a' in [1, 2, 3]
# False
'c' in ['a', 'b', 'c']
# True
'a' not in [1, 2, 3]
# True
'c' not in ['a', 'b', 'c']
# False

Dit werkt ook met tuples, aangezien tuples hashable zijn (als gevolg van het feit dat ze ook onveranderlijk zijn):

(1, 2) in [(3, 4), (1, 2)]
#  True

Als het object op de RHS een __contains__()methode, zal inhet intern aanroepen, zoals vermeld in de laatste paragraaf van de Vergelijkingenvan de documenten.

inen not in,
worden ondersteund door typen die itereerbaar zijn of de
__contains__()methode. U kunt bijvoorbeeld (maar mag niet) dit doen:

[3, 2, 1].__contains__(1)
# True

inkortsluiting, dus als uw element aan het begin van de lijst staat, evalueert insneller:

lst = list(range(10001))
%timeit 1 in lst
%timeit 10000 in lst  # Expected to take longer time.
68.9 ns ± 0.613 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
178 µs ± 5.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Als je meer wilt doen dan alleen controleren of een item in een lijst staat, zijn er opties:

  • list.indexkan worden gebruikt om de index van een item op te halen. Als dat element niet bestaat, wordt een ValueErrorweergegeven.
  • list.countkan worden gebruikt als u de exemplaren wilt tellen.

Het XY-probleem: heb je sets overwogen?

Stel uzelf deze vragen:

  • moet je controleren of een item meer dan één keer in een lijst staat?
  • Is deze controle gedaan binnen een lus, of een functie die herhaaldelijk wordt aangeroepen?
  • Zijn de items die u op uw lijst opslaat hashable? IOW, kun je hashop ze aanroepen?

Als je deze vragen met ‘ja’ hebt beantwoord, zou je in plaats daarvan een setmoeten gebruiken. Een inlidmaatschapstest op lists is O(n) tijdscomplexiteit. Dit betekent dat python een lineaire scan van uw lijst moet maken, elk element moet bezoeken en het moet vergelijken met het zoekitem. Als u dit herhaaldelijk doet of als de lijsten groot zijn, brengt deze bewerking overhead met zich mee.

setobjecten, aan de andere kant, hashen hun waarden voor constante lidmaatschapscontrole. De controle wordt ook gedaan met behulp van in:

1 in {1, 2, 3} 
# True
'a' not in {'a', 'b', 'c'}
# False
(1, 2) in {('a', 'c'), (1, 2)}
# True

Als je de pech hebt dat het element waarnaar je zoekt wel/niet aan het einde van je lijst staat, zal Python de lijst tot het einde hebben gescand. Dit blijkt uit de onderstaande tijden:

l = list(range(100001))
s = set(l)
%timeit 100000 in l
%timeit 100000 in s
2.58 ms ± 58.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
101 ns ± 9.53 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Ter herinnering: dit is een geschikte optie zolang de elementen die je opslaat en opzoekt hashbaar zijn. IOW, het moeten ofwel onveranderlijke typen zijn, of objecten die __hash__implementeren.

Other episodes