web-dev-qa-db-fra.com

Comment rechercher une liste de tuples dans Python

J'ai donc une liste de tuples comme celle-ci:

[(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

Je veux cette liste pour un tuple dont le nombre est égal à quelque chose.

Ainsi, si je fais search(53), il retournera la valeur d'index de 2

Y a-t-il un moyen facile de faire ceci?

88
hdx
[i for i, v in enumerate(L) if v[0] == 53]
85

Vous pouvez utiliser un compréhension de la liste :

>>> a = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]
>>> [x[0] for x in a]
[1, 22, 53, 44]
>>> [x[0] for x in a].index(53)
2
48
Greg Hewgill

tl; dr

Un expression génératrice est probablement la solution la plus performante et la plus simple à votre problème:

l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

result = next((i for i, v in enumerate(l) if v[0] == 53), None)
# 2

Explication

Plusieurs réponses apportent une solution simple à cette question avec une compréhension de liste. Bien que ces réponses soient parfaitement correctes, elles ne sont pas optimales. Selon votre cas d'utilisation, quelques modifications simples peuvent présenter des avantages significatifs.

Le principal problème que je vois avec l’utilisation d’une liste de compréhension pour ce cas d’utilisation est que la liste complète sera traitée, bien que vous souhaitiez seulement trouver 1 élément .

Python fournit une construction simple qui est idéale ici. C'est ce qu'on appelle expression génératrice . Voici un exemple:

# Our input list, same as before
l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

# Call next on our generator expression.
next((i for i, v in enumerate(l) if v[0] == 53), None)

Nous pouvons nous attendre à ce que cette méthode fonctionne fondamentalement de la même manière que les interprétations de liste dans notre exemple trivial, mais qu'en est-il si nous travaillons avec un ensemble de données plus volumineux? C'est là que l'avantage d'utiliser la méthode du générateur entre en jeu. Plutôt que de construire une nouvelle liste, nous utiliserons votre liste existante comme notre itérable, et nous utiliserons next() pour obtenir le premier élément de notre générateur.

Voyons comment ces méthodes fonctionnent différemment sur des ensembles de données plus volumineux. Ce sont de grandes listes, composées de 10000000 + 1 éléments, avec notre objectif au début (meilleur) ou à la fin (pire). Nous pouvons vérifier que ces deux listes fonctionneront de manière égale en utilisant la compréhension de liste suivante:

Liste des compréhensions

"Pire cas"

worst_case = ([(False, 'F')] * 10000000) + [(True, 'T')]
print [i for i, v in enumerate(worst_case) if v[0] is True]

# [10000000]
#          2 function calls in 3.885 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    3.885    3.885    3.885    3.885 so_lc.py:1(<module>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

"Meilleur cas"

best_case = [(True, 'T')] + ([(False, 'F')] * 10000000)
print [i for i, v in enumerate(best_case) if v[0] is True]

# [0]
#          2 function calls in 3.864 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    3.864    3.864    3.864    3.864 so_lc.py:1(<module>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Expressions de générateur

Voici mon hypothèse pour les générateurs: nous verrons que les générateurs auront de meilleures performances dans le meilleur des cas, mais de la même manière dans le pire des cas. Ce gain de performance est principalement dû au fait que le générateur est évalué paresseusement, ce qui signifie qu'il ne calculera que ce qui est nécessaire pour obtenir une valeur.

Pire cas

# 10000000
#          5 function calls in 1.733 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         2    1.455    0.727    1.455    0.727 so_lc.py:10(<genexpr>)
#         1    0.278    0.278    1.733    1.733 so_lc.py:9(<module>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
#         1    0.000    0.000    1.455    1.455 {next}

Meilleur cas

best_case  = [(True, 'T')] + ([(False, 'F')] * 10000000)
print next((i for i, v in enumerate(best_case) if v[0] == True), None)

# 0
#          5 function calls in 0.316 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    0.316    0.316    0.316    0.316 so_lc.py:6(<module>)
#         2    0.000    0.000    0.000    0.000 so_lc.py:7(<genexpr>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
#         1    0.000    0.000    0.000    0.000 {next}

QUOI?! Le meilleur des cas efface la compréhension de la liste, mais je ne m'attendais pas à ce que notre pire des cas surpasse de loin celle de la liste. Comment c'est? Franchement, je ne pouvais que spéculer sans autre recherche.

Tout cela avec un grain de sel, je n’ai pas fait de profilage robuste ici, mais juste quelques tests de base. Cela devrait être suffisant pour comprendre que l'expression d'un générateur est plus performante pour ce type de recherche dans une liste.

Notez que tout cela est basique et intégré à Python. Nous n'avons besoin d'importer ou d'utiliser aucune bibliothèque.

J'ai d'abord vu cette technique pour chercher dans le cours dacity cs212 avec Peter Norvig.

42
Jon Surrell

Vos tuples sont essentiellement des paires clé-valeur - a python dict-- donc:

l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]
val = dict(l)[53]

Edit - aha, vous dites que vous voulez la valeur d’index de (53, "xuxa"). Si c'est ce que vous voulez vraiment, vous devrez parcourir la liste d'origine ou peut-être créer un dictionnaire plus compliqué:

d = dict((n,i) for (i,n) in enumerate(e[0] for e in l))
idx = d[53]
26
Andrew Jaffe

Hmm ... eh bien, la façon simple qui me vient à l'esprit est de le convertir en dict

d = dict(thelist)

et accès d[53].

EDIT : Oups, vous avez mal interprété votre question la première fois. Il semble que vous souhaitiez réellement obtenir l'index où un nombre donné est stocké. Dans ce cas, essayez

dict((t[0], i) for i, t in enumerate(thelist))

au lieu d'une ancienne conversion dict ordinaire. Ensuite d[53] serait 2.

12
David Z

En supposant que la liste soit longue et que les nombres se répètent, envisagez d'utiliser le type SortedList du module module conteneurs triés Python . Le type SortedList maintiendra automatiquement les n-uplets dans l'ordre par numéro et permettra une recherche rapide.

Par exemple:

from sortedcontainers import SortedList
sl = SortedList([(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")])

# Get the index of 53:

index = sl.bisect((53,))

# With the index, get the Tuple:

tup = sl[index]

Cela fonctionnera beaucoup plus rapidement que la suggestion de compréhension de liste en effectuant une recherche binaire. La suggestion du dictionnaire sera encore plus rapide mais ne fonctionnera pas s'il peut y avoir des numéros en double avec des chaînes différentes.

S'il existe des numéros en double avec différentes chaînes, vous devez effectuer une étape supplémentaire:

end = sl.bisect((53 + 1,))

results = sl[index:end]

En bissectant pour 54, nous trouverons l'indice final de notre tranche. Ce sera beaucoup plus rapide sur les longues listes par rapport à la réponse acceptée.

6
GrantJ

Juste un autre moyen.

Zip(*a)[0].index(53)
1
RussW