web-dev-qa-db-fra.com

Python recherche dans les listes de listes

J'ai une liste de listes à deux éléments et je dois y chercher des éléments.

Si la liste est:

list =[ ['a','b'], ['a','c'], ['b','d'] ]

Je peux rechercher une paire facilement en faisant

['a','b'] in list

Maintenant, y a-t-il un moyen de voir si j'ai une paire dans laquelle une chaîne est présente dans la deuxième position? Je peux le faire:

for i in range (0, len(list)):
    if list[i][1]==search:
       found=1

Mais y a-t-il un (meilleur) moyen sans la boucle for? Je n'ai pas besoin de connaître i ou de continuer la boucle après l'avoir trouvée.

48
greye

Vous allez toujours avoir une boucle - quelqu'un pourrait venir avec un habile one-liner qui cache la boucle dans un appel à map() ou similaire, mais il sera toujours là.

Ma préférence serait toujours d'avoir un code propre et simple, sauf si les performances sont un facteur majeur.

Voici peut-être une version plus Pythonic de votre code:

data = [['a','b'], ['a','c'], ['b','d']]
search = 'c'
for sublist in data:
    if sublist[1] == search:
        print "Found it!", sublist
        break
# Prints: Found it! ['a', 'c']

Il sort de la boucle dès qu'il trouve une correspondance.

(Vous avez une faute de frappe, en passant, dans ['b''d'].)

37
RichieHindle

Voici la manière pythonique de le faire:

data = [['a','b'], ['a','c'], ['b','d']]
search = 'c'
any(e[1] == search for e in data)

Ou ... eh bien, je ne prétends pas que c'est la "seule vraie méthode pythonique", car à un moment donné, cela devient un peu subjectif ce qui est Pythonic et ce qui ne l'est pas, ou quelle méthode est plus pythonique que un autre. Mais utiliser any() est définitivement un style Python plus typique qu’une boucle for, comme par exemple. Réponse de RichieHindle

Bien sûr, il existe une boucle cachée dans l’implémentation de any, bien qu’elle sorte de la boucle dès qu’elle trouve une correspondance.


Depuis que je m'ennuyais, j'ai créé un script de synchronisation pour comparer les performances des différentes suggestions, en modifiant certaines d'entre elles si nécessaire pour que l'API soit identique. Maintenant, nous devons garder à l'esprit que le plus rapide n'est pas toujours le meilleur, et être rapide n'est pas la même chose qu'être Pythonic. Cela étant dit, les résultats sont ... étranges. Apparemment, les boucles for sont très rapides, ce qui n'est pas ce à quoi je m'attendais, alors je les prendrais avec un grain de sel sans comprendre pourquoi elles sont sorties comme elles le sont.

Quoi qu'il en soit, lorsque j'utilise la liste définie dans la question avec trois sous-listes de deux éléments chacune, du plus rapide au plus lent, j'obtiens ces résultats:

  1. Réponse de RichieHindle avec la boucle for, pointant à 0,22 μs
  2. Première suggestion de Terence Honles qui crée une liste, à 0,36 μs
  3. Réponse de Pierre-Luc Bedard (dernier bloc de code) , à 0,43 μs
  4. Essentiellement liée entre la réponse de Markus et la boucle for de la question initiale , à 0,48 μs
  5. Réponse de Coady en utilisant operator.itemgetter(), à 0,53 μs
  6. Assez proche pour compter comme une égalité entre la réponse d'Alex Martelli avec ifilter() et la réponse d'Anon , à 0,67 μs (celle d'Alex est toujours plus rapide d'une demi-microseconde)
  7. Un autre lien assez étroit entre la réponse de jojo , le mien, celui de Brandon E Taylor (qui est identique au mien), et la deuxième suggestion de Terence Honles utilisant any(), le tout venant à 0,81-0,82. μs
  8. Et ensuite la réponse de l'utilisateur27221 en utilisant des compréhensions de liste imbriquées, à 0,95 μs

Évidemment, les minuteries réelles ne sont significatives pour aucun autre matériel, mais les différences entre eux devraient donner une idée de la proximité des différentes méthodes.

Lorsque j'utilise une liste plus longue, les choses changent un peu. J'ai commencé avec la liste de la question, avec trois sous-listes, et ajouté 197 autres sous-listes, pour un total de 200 sous-listes chacune de longueur deux. En utilisant cette liste plus longue, voici les résultats:

  1. Réponse de RichieHindle , au même 0,22 μs qu'avec la liste plus courte
  2. Réponse de Coady en utilisant operator.itemgetter(), toujours à 0,53 μs
  3. Première suggestion de Terence Honles qui crée une liste, à 0,36 μs
  4. Autre lien virtuel entre la réponse d’Alex Martelli avec ifilter() et la réponse d’Anon , à 0,67 μs
  5. Là encore, un lien assez étroit entre ma réponse, la méthode de Brandon E Taylor identique, et la deuxième suggestion de Terence Honles en utilisant any(), le tout arrivant à 0,81-0,82 μs

Ce sont ceux qui conservent leur calendrier d'origine lorsque la liste est étendue. Les autres, qui ne le font pas, sont

  1. La boucle for de la question initiale , à 1,24 μs
  2. Première suggestion de Terence Honles qui crée une liste, à 7,49 μs
  3. Réponse de Pierre-Luc Bedard (dernier bloc de code) , à 8,12 μs
  4. Réponse de Markus , à 10,27 μs
  5. réponse de jojo , à 19,87 μs
  6. Et enfin la réponse de l'utilisateur27221 en utilisant des compréhensions de liste imbriquées, à 60,59 μs
64
David Z
>>> the_list =[ ['a','b'], ['a','c'], ['b''d'] ]
>>> any('c' == x[1] for x in the_list)
True
16
Brandon E Taylor

le dessus tout bon

mais voulez-vous garder le résultat?

si c'est le cas...

vous pouvez utiliser ce qui suit

result = [element for element in data if element[1] == search]

alors un simple

len(result)

vous permet de savoir si quelque chose a été trouvé (et maintenant vous pouvez faire des choses avec les résultats)

bien sur cela ne traite pas les éléments dont la longueur est inférieure à un (que vous devriez vérifier sauf si vous savez qu'ils sont toujours supérieurs à longueur 1, et dans ce cas si vous utilisez un tuple ? (les tuples sont immuables))

si vous savez que tous les articles ont une longueur définie, vous pouvez aussi faire:

any(second == search for _, second in data)

ou pour len (data [0]) == 4:

any(second == search for _, second, _, _ in data)

... et je recommanderais d'utiliser

for element in data:
   ...

au lieu de

for i in range(len(data)):
   ...

(pour les utilisations futures, sauf si vous souhaitez enregistrer ou utiliser 'i', et pour que vous sachiez que le '0' n'est pas obligatoire, vous devez uniquement utiliser la syntaxe complète si vous démarrez à une valeur non nulle)

13
Terence Honles
>>> my_list =[ ['a', 'b'], ['a', 'c'], ['b', 'd'] ]
>>> 'd' in (x[1] for x in my_list)
True

Modification pour ajouter:

La réponse de David en utilisant any et la mienne en utilisant in se terminent lorsqu'elles trouvent une correspondance car nous utilisons des expressions génératrices. Voici un test utilisant un générateur infini pour montrer que:

def mygen():
    ''' Infinite generator '''
    while True:
        yield 'xxx'  # Just to include a non-match in the generator
        yield 'd'

print 'd' in (x for x in mygen())     # True
print any('d' == x for x in mygen())  # True
# print 'q' in (x for x in mygen())     # Never ends if uncommented
# print any('q' == x for x in mygen())  # Never ends if uncommented

J'aime simplement utiliser dans au lieu de deux == et any .

8
Anon

Qu'en est-il de:

list =[ ['a','b'], ['a','c'], ['b','d'] ]
search = 'b'

filter(lambda x:x[1]==search,list)

Cela retournera chaque liste dans la liste des listes avec le deuxième élément égal à search. 

6
jojo

Markus a un moyen d'éviter d'utiliser Word for - en voici un autre, qui devrait offrir de bien meilleures performances pour les the_lists longs ...:

import itertools
found = any(itertools.ifilter(lambda x:x[1]=='b', the_list)
4
Alex Martelli

Rien de mal à utiliser une exp gén, mais si l'objectif est d'aligner la boucle ...

>>> import itertools, operator
>>> 'b' in itertools.imap(operator.itemgetter(1), the_list)
True

Devrait être le plus rapide aussi.

3
A. Coady

k ancien message, mais personne ne doit utiliser une expression de liste pour répondre: P

list =[ ['a','b'], ['a','c'], ['b','d'] ]
Search = 'c'

# return if it find in either item 0 or item 1
print [x for x,y in list if x == Search or y == Search]

# return if it find in item 1
print [x for x,y in list if y == Search]
2
Pierre-Luc Bedard
>>> the_list =[ ['a','b'], ['a','c'], ['b','d'] ]
>>> "b" in Zip(*the_list)[1]
True

Zip() prend un tas de listes et regroupe des éléments par index, en transposant efficacement la matrice des listes de listes. L'astérisque prend le contenu de the_list et l'envoie à Zip sous forme d'arguments. Vous passez donc les trois listes séparément, comme le souhaite Zip. Il ne reste plus qu'à vérifier si "b" (ou autre) est dans la liste d'éléments contenant l'index qui vous intéresse.

1
Markus

Je pense que l'utilisation de la compréhension de liste imbriquée est le moyen le plus élégant de résoudre ce problème, car le résultat intermédiaire correspond à la position de l'élément. Une implémentation serait:

list =[ ['a','b'], ['a','c'], ['b','d'] ]
search = 'c'
any([ (list.index(x),x.index(y)) for x in list for y in x if y == search ] )
0
user27221

Je cherchais une profonde recherche pour les dictionnaires et je n’en ai pas trouvé. Sur la base de cet article, j'ai pu créer les éléments suivants. Merci et amusez-vous!

def deapFind( theList, key, value ):
    result = False
    for x in theList:
        if( value == x[key] ):
            return True
    return result

theList = [{ "n": "aaa", "d": "bbb" }, { "n": "ccc", "d": "ddd" }]
print 'Result: ' + str (deapFind( theList, 'n', 'aaa'))

J'utilise == au lieu de l'opérateur in puisque in renvoie true pour les correspondances partielles. IOW: la recherche de aa sur la touche n renvoie true. Je ne pense pas que cela serait souhaité.

HTH

0
Keith