Python: Rechercher dans la liste

587

Je suis tombé sur ceci:

item = someSortOfSelection()
if item in myList:
    doMySpecialFunction(item)

mais parfois cela ne fonctionne pas avec tous mes éléments, comme s'ils n'étaient pas reconnus dans la liste (quand c'est une liste de chaîne).

Est - ce la façon la plus « pythonique » de trouver un élément dans une liste: if x in l:?

Stéphane Rolland
la source
3
C'est parfaitement bien et devrait fonctionner si l'élément est égal à l'un des éléments à l'intérieur myList.
Niklas B.
1
voulez-vous dire que c'était la bonne façon de faire les choses? dans mes plusieurs essais, il y avait peut-être des espaces blancs et des sauts de ligne interférant ... je voulais juste être sûr que c'était le bon moyen d'implémenter "trouver dans la liste" (en général)
Stephane Rolland

Réponses:

1174

Quant à votre première question: ce code est parfaitement bien et devrait fonctionner s'il itemest égal à l'un des éléments à l'intérieur myList. Peut-être que vous essayez de trouver une chaîne qui ne correspond pas exactement à l'un des éléments ou que vous utilisez une valeur flottante qui souffre d'inexactitude.

Quant à votre deuxième question: il existe en fait plusieurs façons possibles de "trouver" des choses dans les listes.

Vérifier si quelque chose est à l'intérieur

Voici le cas d'utilisation que vous décrivez: Vérifier si quelque chose se trouve dans une liste ou non. Comme vous le savez, vous pouvez utiliser l' inopérateur pour cela:

3 in [1, 2, 3] # => True

Filtrer une collection

Autrement dit, trouver tous les éléments dans une séquence qui remplissent une certaine condition. Vous pouvez utiliser la compréhension de liste ou des expressions génératrices pour cela:

matches = [x for x in lst if fulfills_some_condition(x)]
matches = (x for x in lst if x > 6)

Ce dernier renverra un générateur que vous pouvez imaginer comme une sorte de liste paresseuse qui ne sera construite que lorsque vous l'itérerez. Soit dit en passant, le premier est exactement équivalent à

matches = filter(fulfills_some_condition, lst)

en Python 2. Ici, vous pouvez voir des fonctions d'ordre supérieur à l'œuvre. En Python 3, filterne renvoie pas de liste, mais un objet de type générateur.

Trouver la première occurrence

Si vous ne voulez que la première chose qui corresponde à une condition (mais vous ne savez pas encore ce que c'est), c'est bien d'utiliser une boucle for (éventuellement en utilisant également la elseclause, qui n'est pas vraiment bien connue). Vous pouvez aussi utiliser

next(x for x in lst if ...)

qui retournera la première correspondance ou lèvera un StopIterationsi aucun n'est trouvé. Vous pouvez également utiliser

next((x for x in lst if ...), [default value])

Recherche de l'emplacement d'un élément

Pour les listes, il y a aussi la indexméthode qui peut parfois être utile si vous voulez savoir où se trouve un certain élément dans la liste:

[1,2,3].index(2) # => 1
[1,2,3].index(4) # => ValueError

Cependant, notez que si vous avez des doublons, .indexrenvoie toujours l'index le plus bas: ......

[1,2,3,2].index(2) # => 1

S'il y a des doublons et que vous voulez tous les index, vous pouvez utiliser à la enumerate()place:

[i for i,x in enumerate([1,2,3,2]) if x==2] # => [1, 3]
Niklas B.
la source
10
Stéphane: Permettez-moi de reformuler: ce if x in listn'est pas la chose que les gens se plaignent de ne pas être une fonction intégrée. Ils se plaignent du fait qu'il n'existe aucun moyen explicite de trouver la première occurrence de quelque chose dans une liste qui correspond à une certaine condition. Mais comme indiqué dans ma réponse, next()peut être (ab) utilisé pour cela.
Niklas B.
3
@Stephane: Le second ne génère pas de tuple, mais un générateur (qui est une liste pas encore construite, en gros). Si vous ne souhaitez utiliser le résultat qu'une seule fois, un générateur est généralement préférable. Cependant, si vous souhaitez utiliser la collection créée plusieurs fois par la suite, il est conseillé de créer une liste explicite en premier lieu. Jetez un œil à ma mise à jour, elle est maintenant un peu mieux structurée :)
Niklas B.
26
Votre exemple "trouver la première occurrence" est doré. Se sent plus pythonique que l' [list comprehension...][0]approche
acjay
4
Je suis de plus en plus déçu par les capacités «fonctionnelles» de python. Dans haskell, il existe une fonction de recherche dans le module Data.List qui fait exactement cela. Mais en python, ce n'est pas le cas et c'est trop petit pour en faire une bibliothèque, vous devez donc réimplémenter la même logique encore et encore. Quel gaspillage ...
user1685095
3
Ce serait bien s'il y avait un kwarg à index()appeler keyqui fonctionnait comme keyaccepté par max(); par exemple: index(list, key=is_prime).
Curt
189

Si vous voulez trouver un élément ou Noneutiliser la valeur par défaut dans next, il ne se lèvera pas StopIterationsi l'élément n'a pas été trouvé dans la liste:

first_or_default = next((x for x in lst if ...), None)
Janusz Skonieczny
la source
1
nextprend un itérateur comme premier paramètre et une liste / tuple n'est PAS un itérateur. Il devrait donc être first_or_default = next(iter([x for x in lst if ...]), None)voir docs.python.org/3/library/functions.html#next
Devy
7
@Devy: c'est vrai, mais (x for x in lst if ...)c'est un générateur sur la liste lst(qui est un itérateur). Si vous le faites next(iter([x for x in lst if ...]), None), vous devez construire la liste [x for x in lst if ...], ce qui sera une opération beaucoup plus coûteuse.
Erlend Graff
1
Il y a une abstraction ici pour définir une fonction de recherche. Il suffit d'encapsuler l'expansion booléenne de ifdans un lambda et vous pouvez écrire find(fn,list)généralement au lieu de masquer le code du générateur.
semiomant
22

Bien que la réponse de Niklas B. soit assez complète, lorsque nous voulons trouver un élément dans une liste, il est parfois utile d'obtenir son index:

next((i for i, x in enumerate(lst) if [condition on x]), [default value])
Vincent Cantin
la source
11

Trouver la première occurrence

Il y a une recette pour ça dans itertools:

def first_true(iterable, default=False, pred=None):
    """Returns the first true value in the iterable.

    If no true value is found, returns *default*

    If *pred* is not None, returns the first item
    for which pred(item) is true.

    """
    # first_true([a,b,c], x) --> a or b or c or x
    # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
    return next(filter(pred, iterable), default)

Par exemple, le code suivant recherche le premier nombre impair dans une liste:

>>> first_true([2,3,4,5], None, lambda x: x%2==1)
3  
Antony Hatchkins
la source
6

Une autre alternative: vous pouvez vérifier si un élément est dans une liste avec if item in list:, mais c'est l'ordre O (n). Si vous avez affaire à de grandes listes d'éléments et que tout ce que vous devez savoir est si quelque chose fait partie de votre liste, vous pouvez d'abord convertir la liste en un ensemble et profiter de la recherche d'un ensemble de temps constant :

my_set = set(my_list)
if item in my_set:  # much faster on average than using a list
    # do something

Cela ne sera pas la bonne solution dans tous les cas, mais dans certains cas, cela pourrait vous donner de meilleures performances.

Notez que la création de l'ensemble avec set(my_list)est également O (n), donc si vous n'avez besoin de le faire qu'une seule fois, il n'est pas plus rapide de le faire de cette façon. Si vous devez vérifier à plusieurs reprises l'appartenance, ce sera O (1) pour chaque recherche après la création de l'ensemble initial.

Engineero
la source
4

Vous souhaiterez peut-être utiliser l'une des deux recherches possibles lorsque vous travaillez avec une liste de chaînes:

  1. si l'élément de liste est égal à un élément ('exemple' est dans ['un', 'exemple', 'deux']):

    if item in your_list: some_function_on_true()

    'ex' dans ['un', 'ex', 'deux'] => Vrai

    'ex_1' dans ['un', 'ex', 'deux'] => Faux

  2. si l'élément de liste est comme un élément ('ex' est dans ['un,' exemple ',' deux '] ou' exemple_1 'est dans [' un ',' exemple ',' deux ']):

    matches = [el for el in your_list if item in el]

    ou

    matches = [el for el in your_list if el in item]

    vérifiez-les len(matches)ou lisez-les si nécessaire.

Alexey Antonenko
la source
3

Définition et utilisation

la count()méthode renvoie le nombre d'éléments avec la valeur spécifiée.

Syntaxe

list.count(value)

exemple:

fruits = ['apple', 'banana', 'cherry']

x = fruits.count("cherry")

Exemple de question:

item = someSortOfSelection()

if myList.count(item) >= 1 :

    doMySpecialFunction(item)
josef
la source
2
Est-ce efficace dans une très longue liste? Dis une liste d'un million?
3kstc
1
Je ne suis pas sûr !!!
josef
1

Au lieu d'utiliser list.index(x)qui renvoie l'index de x s'il se trouve dans la liste ou renvoie un #ValueErrormessage si x n'est pas trouvé, vous pouvez utiliser list.count(x)qui renvoie le nombre d'occurrences de x dans la liste (validation que x est bien dans la liste) ou renvoie 0 sinon (en l'absence de x). Ce qui est cool, count()c'est qu'il ne casse pas votre code ou vous oblige à lever une exception lorsque x n'est pas trouvé

Taylor
la source
et la mauvaise, c'est qu'il compte les éléments. Il ne s'arrête pas lorsque l'élément est trouvé. donc les performances sont mauvaises sur les grandes listes
Jean-François Fabre
1

Si vous allez vérifier si la valeur existe une fois dans la collection, alors utiliser l'opérateur 'in' est correct. Cependant, si vous allez vérifier plus d'une fois, je vous recommande d'utiliser le module bissect. Gardez à l'esprit que l'utilisation des données du module bissect doit être triée. Vous triez donc les données une fois et vous pouvez ensuite utiliser la bissect. L'utilisation du module bissect sur ma machine est environ 12 fois plus rapide que l'utilisation de l'opérateur 'in'.

Voici un exemple de code utilisant Python 3.8 et la syntaxe ci-dessus:

import bisect
from timeit import timeit

def bisect_search(container, value):
    return (
      (index := bisect.bisect_left(container, value)) < len(container) 
      and container[index] == value
    )

data = list(range(1000))
# value to search
true_value = 666
false_value = 66666

# times to test
ttt = 1000

print(f"{bisect_search(data, true_value)=} {bisect_search(data, false_value)=}")

t1 = timeit(lambda: true_value in data, number=ttt)
t2 = timeit(lambda: bisect_search(data, true_value), number=ttt)

print("Performance:", f"{t1=:.4f}, {t2=:.4f}, diffs {t1/t2=:.2f}")

Production:

bisect_search(data, true_value)=True bisect_search(data, false_value)=False
Performance: t1=0.0220, t2=0.0019, diffs t1/t2=11.71
Vlad Bezden
la source
0

Vérifiez qu'il n'y a pas d'espace blanc supplémentaire / indésirable dans les éléments de la liste des chaînes. C'est une raison qui peut interférer en expliquant que les articles sont introuvables.

Stéphane Rolland
la source