2010-12-08 3 views
71

J'ai une liste de dicts:Trouvez l'index d'un dict dans une liste, en faisant correspondre la valeur du dict

list = [{'id':'1234','name':'Jason'}, 
     {'id':'2345','name':'Tom'}, 
     {'id':'3456','name':'Art'}] 

Comment puis-je trouver efficacement la position d'index [0], [1], ou [ 2] en faisant correspondre sur name = 'Tom'?

S'il s'agissait d'une liste unidimensionnelle, je pourrais faire list.index() mais je ne sais pas comment procéder en recherchant les valeurs des dicts dans la liste.

+5

Et quelle devrait être la réponse si aucun élément n'est trouvé? déclencher une exception? retourner Aucun? – tokland

+7

Si vous en avez souvent besoin, utilisez une structure de données plus appropriée (peut-être '{'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...} '?) – delnan

+0

@delnan Parce que c'est une recette pour le désastre! Si quelque chose, il devrait être '{'1234': {'name': 'Jason'}, ...}'. Pas que cela aiderait ce cas d'utilisation. – OJFord

Répondre

87
tom_index = next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None) 
# 1 

Si vous devez chercher à plusieurs reprises de nom, vous devriez les indexer par nom (en utilisant un dictionnaire), cette façon obtenir opérations serait O (1) temps. Une idée:

def build_dict(seq, key): 
    return dict((d[key], dict(d, index=index)) for (index, d) in enumerate(seq)) 

info_by_name = build_dict(lst, key="name") 
tom_info = info_by_name.get("Tom") 
# {'index': 1, 'id': '2345', 'name': 'Tom'} 
+1

+1 Battez-moi. –

+0

+1 pour la solution plus générique, ce qui semble bien – aeter

+1

IMHO ce n'est pas aussi lisible ou Pythonic est la réponse @ Emile. Parce que l'intention n'est pas vraiment de créer un générateur (et d'utiliser 'next()' car cela me semble bizarre), le but est simplement d'obtenir l'index. En outre, cela déclenche StopIteration, tandis que la méthode Python 'lst.index()' augmente ValueError. –

4

Il ne sera pas efficace, vous devez marcher sur la liste de vérifier chaque élément dans ce (O (n)). Si vous voulez l'efficacité, vous pouvez utiliser dict de dicts. Sur la question, voici une façon possible de le trouver (bien que, si vous voulez coller à cette structure de données, il est effectivement plus efficace d'utiliser un générateur comme Brent Newey a écrit dans les commentaires, voir aussi la réponse de tokland):

>>> L = [{'id':'1234','name':'Jason'}, 
...   {'id':'2345','name':'Tom'}, 
...   {'id':'3456','name':'Art'}] 
>>> [i for i,_ in enumerate(L) if _['name'] == 'Tom'][0] 
1 
+1

Vous pouvez gagner l'efficacité vous désir en utilisant un générateur. Voir la réponse de Tokland. –

+2

@Brent Newey: Le générateur ne change pas le fait, que vous devez parcourir la liste entière, faisant la recherche O (n) comme aeter prétend ... Selon combien de temps cette liste est, la différence entre l'utilisation d'un générateur vs utiliser une boucle for ou tout ce qui pourrait être négligeable, alors que la différence entre utiliser un dict et utiliser une liste peut ne pas être – Dirk

+0

@Brent: Vous avez raison, mais peut-il battre une recherche O (1) dans un dictionnaire, en outre si la recherche l'article est à la fin de la liste? – aeter

25

une simple version lisible est

def find(lst, key, value): 
    for i, dic in enumerate(lst): 
     if dic[key] == value: 
      return i 
    return -1 
+6

Cela semble le plus lisible et Pythonic. Il imite aussi bien le comportement de 'str.find()'. Vous pouvez également l'appeler 'index()' et élever un 'ValueError' au lieu de retourner -1 si cela était préférable. –

+5

D'accord - en retournant -1 quand aucune correspondance n'est trouvée, vous aurez toujours le dernier dict de la liste, ce qui n'est probablement pas ce que vous voulez. Mieux vaut retourner None et vérifier l'existence d'une correspondance dans le code appelant. – shacker

2

Voici une fonction qui trouve la position de l'index du dictionnaire si elle existe.

dicts = [{'id':'1234','name':'Jason'}, 
     {'id':'2345','name':'Tom'}, 
     {'id':'3456','name':'Art'}] 

def find_index(dicts, key, value): 
    class Null: pass 
    for i, d in enumerate(dicts): 
     if d.get(key, Null) == value: 
      return i 
    else: 
     raise ValueError('no dict with the key and value combination found') 

print find_index(dicts, 'name', 'Tom') 
# 1 
find_index(dicts, 'name', 'Ensnare') 
# ValueError: no dict with the key and value combination found 
0

semble le plus logique d'utiliser un filtre/combo index:

names=[{}, {'name': 'Tom'},{'name': 'Tony'}] 
names.index(filter(lambda n: n.get('name') == 'Tom', names)[0]) 
1 

Et si vous pensez qu'il pourrait y avoir plusieurs matches:

[names.index(n) for item in filter(lambda n: n.get('name') == 'Tom', names)] 
[1] 
0

Pour une donnée itératives, more_itertools.locate cède des positions des éléments qui satisfont un prédicat.

import more_itertools as mit 

iterable = [ 
    {"id": "1234", "name": "Jason"}, 
    {"id": "2345", "name": "Tom"}, 
    {"id": "3456", "name": "Art"} 
] 

list(mit.locate(iterable, pred=lambda d: d["name"] == "Tom")) 
# [1] 

more_itertools est une bibliothèque tierce qui implémente itertools recipes entre autres outils utiles. "List" est le constructeur de liste, vous feriez mieux de choisir un autre nom pour une liste (même dans un exemple)

Questions connexes