web-dev-qa-db-fra.com

Accéder aux éléments du dictionnaire par position dans Python 3.6+ efficacement

Je comprends que les dictionnaires sont insertion ordonnée dans Python 3.6 + , comme détail d'implémentation dans 3.6 et officiel dans 3.7+.

Étant donné qu'ils sont ordonnés, il semble étrange qu'aucune méthode n'existe pour récupérer le i e élément d'un dictionnaire par ordre d'insertion. Les seules solutions disponibles semblent avoir une complexité O ( n ), soit:

  1. Convertir en liste via un processus O ( n ) puis utiliser list.__getitem__.
  2. enumerate éléments de dictionnaire dans une boucle et retourne la valeur lorsque l'index souhaité est atteint. Encore une fois, avec O ( n ) complexité temporelle.

Étant donné que l'obtention d'un élément à partir d'un list a une complexité O(1)), existe-t-il un moyen d'obtenir la même complexité avec les dictionnaires? Soit avec le dict ou collections.OrderedDict travaillerait.

Si ce n'est pas possible, existe-t-il une raison structurelle empêchant une telle méthode, ou s'agit-il simplement d'une fonctionnalité qui n'a pas encore été envisagée/mise en œuvre?

26
jpp

Pour un OrderedDict c'est intrinsèquement O(n) parce que l'ordre est enregistré dans un liste liée .

Pour le dict intégré, il y a un vecteur (un tableau contigu) plutôt qu'une liste liée, mais à peu près la même chose à la fin: le vecteur contient quelques sortes de "nuls", des valeurs internes spéciales qui signifient "aucune clé n'a été encore stocké ici "ou" une clé était stockée ici mais plus ". Cela rend, par exemple, la suppression d'une clé extrêmement bon marché (il suffit d'écraser la clé avec une valeur fictive).

Mais sans ajouter des structures de données auxiliaires en plus de cela, il n'y a aucun moyen de sauter les mannequins sans les marcher un par un. Parce que Python utilise une forme d'adressage ouvert pour la résolution des collisions et maintient le facteur de charge sous 2/3, au moins un tiers des entrées du vecteur sont mannequins. the_vector[i] est accessible en O(1) temps, mais n'a vraiment aucune relation prévisible avec la ième entrée non fictive.

36
Tim Peters

Selon réponse de @ TimPeters , il existe des raisons structurelles pour lesquelles vous ne pouvez pas accéder aux éléments du dictionnaire par position dans O(1) time).

Cela vaut la peine d'envisager les alternatives si vous recherchez O(1) recherche par clé ou position. Il y a 3ème bibliothèques de partie telles que NumPy/Pandas qui offrent une telle fonctionnalité, efficace en particulier pour les tableaux numériques où les pointeurs ne sont pas nécessaires.

Avec Pandas, vous pouvez construire une série "semblable à un dictionnaire" avec des étiquettes uniques offrant une recherche O(1) par "étiquette" ou position. Ce que vous sacrifiez, ce sont les performances lors de la suppression d'une étiquette, ce qui entraîne O ( n ) coût, un peu comme list.

import pandas as pd

s = pd.Series(list(range(n)))

# O(n) item deletion
del s[i]
s.drop(i)
s.pop(i)

# O(1) lookup by label
s.loc[i]
s.at[i]
s.get(i)
s[i]

# O(1) lookup by position
s.iloc[i]
s.iat[i]

pd.Series n'est en aucun cas un remplacement direct de dict. Par exemple, les clés en double ne sont pas empêchées et entraîneront des problèmes si la série est utilisée principalement comme mappage. Cependant, lorsque les données sont stockées dans un bloc de mémoire contigu, comme dans l'exemple ci-dessus, vous pouvez constater des améliorations significatives des performances.

Voir également:

  1. Quels sont les avantages de NumPy par rapport aux listes régulières Python? .
  2. Quel est l'impact sur les performances des index non uniques dans les pandas?
  3. La recherche Pandas DataFrame est un temps linéaire ou un temps constant?
3
jpp