web-dev-qa-db-fra.com

Recherche efficace de dictionnaire?

J'avais une question rapide sur l'efficacité de la recherche dans les dictionnaires large en Python. Je lis un grand fichier séparé par des virgules et j'obtiens une clé et une valeur de chaque ligne. Si ma clé est déjà dans le dictionnaire, j'ajoute la valeur à la valeur répertoriée dans le dictionnaire, si la clé n'existe pas dans le dictionnaire, j'ajoute simplement la valeur. Auparavant, j'utilisais ceci:

if key in data_dict.keys():
    add values
else:
    data_dict[key] = value

Cela commence assez rapidement, mais à mesure que le dictionnaire se développe, il devient de plus en plus lent, au point que je ne peux plus l'utiliser du tout. J'ai changé la façon dont je recherche la clé dans le dictionnaire pour ceci:

try:
    # This will fail if key not present
    data_dict[keyStr] = input_data[keyStr] + load_val
except:
    data_dict[keyStr] = load_val

C'est infiniment plus rapide et peut lire/écrire plus de 350 000 lignes de code en 3 secondes.

Ma question était: pourquoi la commande if key in data_dict.keys(): prend-elle trop de temps que l'appel à try: data_dict[keyStr]? Et pourquoi ne pas Python utiliser l'instruction try lors de la recherche d'une clé dans un dictionnaire?

22
Brumdog22

Le problème est que pour chaque test, vous générez une nouvelle liste de clés avec .keys(). À mesure que la liste des clés s'allonge, le temps requis augmente. Aussi comme noté par dckrooney , la recherche de la clé devient linéaire au lieu de profiter de la structure de table de hachage du dictionnaire.

Remplacer par:

if key in data_dict:
31
Mark Ransom

data_dict.keys() renvoie une liste non triée de clés dans le dictionnaire. Ainsi, chaque fois que vous vérifiez si une clé donnée se trouve dans le dictionnaire, vous effectuez une recherche linéaire dans la liste des clés (une opération O(n)). Plus votre liste est longue , plus il faut de temps pour rechercher une clé donnée.

Comparez cela à data_dict[keyStr]. Cela effectue une recherche de hachage, qui est une opération O(1). Elle ne dépend pas (directement) du nombre de clés dans le dictionnaire; même lorsque vous ajoutez d'autres clés, l'heure pour vérifier si une clé donnée est dans le dictionnaire reste constante.

8
dckrooney

Vous pouvez également simplement utiliser

if key in data_dict:

au lieu de

 if key in data_dict.keys():

Comme mentionné, le premier est une recherche directe de hachage - le décalage prévu est calculé directement, puis vérifié - il s'agit approximativement de O (1), tandis que la vérification des clés est une recherche linéaire, qui est O (n).

In [258]: data_dict = dict([(x, x) for x in range(100000)])

In [259]: %timeit 999999 in data_dict.keys()
100 loops, best of 3: 3.47 ms per loop

In [260]: %timeit 999999 in data_dict
10000000 loops, best of 3: 49.3 ns per loop
5
Corley Brigman

Comme plusieurs autres l'ont noté, le problème réside dans le fait que key in data_dict.keys() utilise le non ordonné list renvoyé par la méthode keys() (dans Python 2.x), ce qui prend temps linéaire O (n) à parcourir, ce qui signifie que le temps d'exécution augmente linéairement avec la taille du dictionnaire, plus la génération de la liste des clés elle-même prendre de plus en plus de temps à mesure que la taille augmente.

D'autre part, key in data_dict ne nécessite qu'un temps constant O (1), en moyenne, pour effectuer une recherche quelle que soit la taille de le dictionnaire car en interne il fait une recherche table de hachage . De plus, cette table de hachage existe déjà depuis sa partie de la représentation interne des dictionnaires, et n'a donc pas besoin d'être générée avant de l'utiliser.

Python ne le fait pas automatiquement car l'opérateur in ne connaît que le type de ses deux opérandes, pas leurs sources, il ne peut donc pas optimiser automatiquement le premier cas où tout ce qu'il voit est la clé et une liste.

Cependant, dans ce cas, le problème de la vitesse de recherche peut probablement être complètement évité en stockant les données dans une version spécialisée d'un dictionnaire appelé defaultdict trouvé dans le collections module. Voici à quoi pourrait ressembler votre code si vous en utilisiez un:

from collections import defaultdict

input_data = defaultdict(float)  # (guessing factory type)
...
data_dict[keyStr] = input_data[keyStr] + load_val

Lorsqu'il n'y a pas d'entrée préexistante pour input_data[keyStr] un sera généré automatiquement avec une valeur par défaut (0.0 pour float dans cet exemple). Comme vous pouvez le voir, le code est plus court et très probablement plus rapide, le tout sans avoir besoin de tests if ou de gestion des exceptions.

4
martineau

Cela ne répond pas à la question, mais l'évite plutôt. Essayez d'utiliser collections.defaultdict. Vous n'aurez pas besoin du if/else ou try/except.

from collections import defaultdict

data_dict = defaultdict(list)
for keyStr, load_val in data:
    data_dict[keyStr].append(load_val)
4
wflynny

En effet, data_dict.keys() renvoie un list contenant les clés du dictionnaire (au moins dans Python 2.x). Ce qui, pour rechercher si une clé est dans la liste, nécessite une recherche linéaire.

Tandis que, essayer d'accéder à un élément du dict profite directement des propriétés impressionnantes des dictionnaires, donc l'accès est presque instantané.

3
William Gaul

Dans le passé, nous utilisions setdefault:

data_dict.setdefault(keyStr, []).append(load_val)
2
Christian Heimes

Il y a quelque chose qui ressemble à la fonction try qui devrait vous aider: dict.get(key, default)

data_dict[keyStr] = data_dict.get(keyStr, '') + load_val
1
SQLesion

Comme analyse supplémentaire, j'ai fait un test de performance simple pour voir comment la méthode try/except mentionnée dans la question se compare à la solution proposée d'utiliser "si la clé dans data_dict" au lieu de "si la clé dans data_dict.keys ()" (je suis en utilisant Python 3.7):

    import timeit

    k = '84782005' # this keys exists in the dictionary
    def t1():
        if k in data_dict:
            pass
    def t2():
        if k in data_dict.keys():
            pass
    def t3():
        try:
            a = data_dict[k]
        except:
            pass

    print(timeit.timeit(t1,number= 100000))
    print(timeit.timeit(t2,number= 100000))
    print(timeit.timeit(t3,number= 100000))

    >> 0.01741484600097465
    >> 0.025949209000827977
    >> 0.017266065000512754

Pour la clé qui existe déjà dans le dictionnaire, le temps de recherche semble être le même pour try/except et la solution proposée. Cependant, si la clé n'existe pas:

    k = '8' # this keys does NOT exist in the dictionary
    def t1():
        if k in data_dict:
            pass
    def t2():
        if k in data_dict.keys():
            pass
    def t3():
        try:
            a = data_dict[k]
        except:
            pass

    print(timeit.timeit(t1,number= 100000))
    print(timeit.timeit(t2,number= 100000))
    print(timeit.timeit(t3,number= 100000))

    >> 0.014406295998924179
    >> 0.0236777299996902
    >> 0.035819852999338764

L'exception semble prendre beaucoup plus de temps que d'utiliser même '.keys ()'! J'appuie donc la solution proposée par Mark.

0
pegah