SortedList <>, SortedDictionary <> et Dictionary <>

98

Je trouve cela SortedList<TKey, TValue> SortedDictionary<TKey, TValue>et Dictionary<TKey, TValue>implémente les mêmes interfaces.

  1. Quand devrions-nous opter pour SortedListet SortedDictionaryplus Dictionary?
  2. Quelle est la différence entre SortedListet SortedDictionaryen termes d'application?
Séparer
la source

Réponses:

101
  1. Lors de l'itération sur les éléments de l'un des deux, les éléments seront triés. Pas si avec Dictionary<T,V>.

  2. MSDN traite la différence entre SortedList<T,V>et SortedDictionary<T,V>:

La classe générique SortedDictionary (TKey, TValue) est un arbre de recherche binaire avec récupération O (log n), où n est le nombre d'éléments dans le dictionnaire. À cet égard, il est similaire à la classe générique SortedList (TKey, TValue). Les deux classes ont des modèles d'objet similaires, et les deux ont une extraction O (log n). Là où les deux classes diffèrent, c'est dans l'utilisation de la mémoire et la vitesse d'insertion et de suppression:

SortedList (TKey, TValue) utilise moins de mémoire que SortedDictionary (TKey, TValue).

SortedDictionary (TKey, TValue) a des opérations d'insertion et de suppression plus rapides pour les données non triées: O (log n) par opposition à O (n) pour SortedList (TKey, TValue).

Si la liste est remplie en une seule fois à partir de données triées, SortedList (TKey, TValue) est plus rapide que SortedDictionary (TKey, TValue).

Szymon Rozga
la source
21
Une autre différence pratique, que dans SortedListvous pouvez récupérer par index (par opposition à récupération par clé) et dans SortedDictionaryvous ne pouvez pas.
Andrew Savinykh du
65

entrez la description de l'image ici

Je mentionnerais la différence entre les dictionnaires.

L'image ci-dessus montre que Dictionary<K,V>c'est égal ou plus rapide dans tous les cas que l' Sortedanalogique, mais si l'ordre des éléments est nécessaire, par exemple pour les imprimer,Sorted un est choisi.

Src: http://people.cs.aau.dk/~normark/oop-csharp/html/notes/collections-note-time-complexity-dictionaries.html

Lev
la source
1
Excellent aperçu. Bien que ce ne soit pas dans la question d'origine, il convient de noter que si vous choisissez entre les Immutableversions de ces dictionnaires, les Sortedversions sont souvent en réalité plus rapides d'environ 40 à 50% que les homologues non triés (toujours O(log(n)), mais nettement plus rapides par opération) . Cependant, les horaires peuvent différer en fonction du tri de l'entrée. Voir stackoverflow.com/a/30638592/111575
Abel
21

Pour résumer les résultats d'un test de performance - SortedList vs SortedDictionary vs Dictionary vs Hashtable , les résultats du meilleur au pire pour différents scénarios:

Utilisation de la mémoire:

SortedList<T,T>
Hashtable
SortedDictionary<T,T>
Dictionary<T,T>

Insertions:

Dictionary<T,T>
Hashtable
SortedDictionary<T,T>
SortedList<T,T>

Opérations de recherche:

Hashtable
Dictionary<T,T>
SortedList<T,T>
SortedDictionary<T,T>

opérations de boucle foreach

SortedList<T,T>
Dictionary<T,T>
Hashtable
SortedDictionary<T,T>
NullReference
la source
1
Lors de l'examen de ces résultats de test, on peut s'interroger sur la raison d'être de SortedDictionary.
beawolf
1
Si vous avez Collectionbesoin de l'être, sortedvous pouvez oublier Hashtableet Dictionary: si vous remplissez votre collection d'un seul coup -> optez pour SortedList, mais si vous prévoyez que vous aurez souvent besoin de .Addet d' .Removeéléments -> optez pour SortedDictionary.
Ama
Il est peut-être nécessaire de clarifier ce que cela sortedsignifie: lorsque vous faites a For Each MyItem in Collectionplutôt que d'être traité dans l'ordre dans lequel vous avez initialement .Addédité les éléments, a sorted Collectionles traitera dans un ordre selon des critères sur les Keyvaleurs (définis dans un IComparer). Par exemple, si vos clés sont des chaînes, votre collection sera par défaut traitée selon l'ordre alphabétique de vos clés, mais vous pouvez toujours définir une règle de tri personnalisée.
Ama
9
  1. Lorsque vous souhaitez que la collection soit triée par clé lorsque vous la parcourez. Si vous n'avez pas besoin de trier vos données, vous êtes mieux avec juste un dictionnaire, il aura de meilleures performances.

  2. SortedList et SortedDictionary font à peu près la même chose, mais sont implémentés différemment, ont donc des forces et des faiblesses différentes expliquées ici .

Meta-Knight
la source
8

Je vois que les réponses proposées se concentrent sur la performance. L'article fourni ci-dessous n'apporte rien de nouveau concernant les performances, mais il explique les mécanismes sous-jacents. Notez également qu'il ne se concentre pas sur les trois Collectiontypes mentionnés dans la question, mais aborde tous les types de l' System.Collections.Genericespace de noms.

http://geekswithblogs.net/BlackRabbitCoder/archive/2011/06/16/c.net-fundamentals-choosing-the-right-collection-class.aspx

Extraits:

Dictionnaire <>

Le dictionnaire est probablement la classe de conteneurs associatifs la plus utilisée. Le dictionnaire est la classe la plus rapide pour les recherches / insertions / suppressions associatives car il utilise une table de hachage sous les couvertures . Étant donné que les clés sont hachées, le type de clé doit correctement implémenter GetHashCode () et Equals () de manière appropriée ou vous devez fournir un IEqualityComparer externe au dictionnaire lors de la construction. Le temps d'insertion / suppression / recherche des éléments dans le dictionnaire est amorti en temps constant - O (1) - ce qui signifie que quelle que soit la taille du dictionnaire, le temps nécessaire pour trouver quelque chose reste relativement constant. Ceci est hautement souhaitable pour les recherches à grande vitesse. Le seul, inconvénient est que le dictionnaire, de par la nature de l'utilisation d'une table de hachage, n'est pas ordonné, doncvous ne pouvez pas facilement parcourir les éléments d'un dictionnaire dans l'ordre .

SortedDictionary <>

Le SortedDictionary est similaire au Dictionary dans son utilisation mais très différent dans sa mise en œuvre. Le SortedDictionary utilise une arborescence binaire sous les couvertures pour maintenir les éléments dans l'ordre par la clé . En conséquence du tri, le type utilisé pour la clé doit implémenter correctement IComparable afin que les clés puissent être correctement triées. Le dictionnaire trié échange un peu de temps de recherche pour la capacité de maintenir les éléments dans l'ordre, ainsi les temps d'insertion / suppression / recherche dans un dictionnaire trié sont logarithmiques - O (log n). De manière générale, avec le temps logarithmique, vous pouvez doubler la taille de la collection et il suffit d'effectuer une comparaison supplémentaire pour trouver l'élément. Utilisez le SortedDictionary lorsque vous souhaitez des recherches rapides, mais que vous souhaitez également pouvoir maintenir la collection dans l'ordre par la clé.

SortedList <>

La SortedList est l'autre classe de conteneurs associatifs triés dans les conteneurs génériques. Une fois de plus, SortedList, comme SortedDictionary, utilise une clé pour trier les paires clé-valeur . Contrairement à SortedDictionary, cependant, les éléments d'une SortedList sont stockés sous forme de tableau trié d'éléments. Cela signifie que les insertions et les suppressions sont linéaires - O (n) - car la suppression ou l'ajout d'un élément peut impliquer de déplacer tous les éléments vers le haut ou vers le bas dans la liste. Cependant, le temps de recherche est O (log n) car la SortedList peut utiliser une recherche binaire pour trouver n'importe quel élément de la liste par sa clé. Alors, pourquoi voudriez-vous faire ça? Eh bien, la réponse est que si vous allez charger la SortedList à l'avance, les insertions seront plus lentes, mais comme l'indexation de tableau est plus rapide que de suivre des liens d'objet, les recherches sont légèrement plus rapides qu'un SortedDictionary. Encore une fois, j'utiliserais ceci dans les situations où vous voulez des recherches rapides et que vous souhaitez maintenir la collection dans l'ordre par la clé, et où les insertions et les suppressions sont rares.


Résumé provisoire des procédures sous-jacentes

Les commentaires sont les bienvenus car je suis sûr que je n'ai pas tout bien fait.

  • Tous les tableaux sont de taille n.
  • Tableau non trié = .Add / .Remove est O (1), mais .Item (i) est O (n).
  • Tableau trié = .Add / .Remove est O (n), mais .Item (i) est O (log n).

dictionnaire

Mémoire

KeyArray(n) -> non-sorted array<pointer>
ItemArray(n) -> non-sorted array<pointer>
HashArray(n) -> sorted array<hashvalue>

Ajouter

  1. Ajouter HashArray(n) = Key.GetHash# O (1)
  2. Ajouter KeyArray(n) = PointerToKey# O (1)
  3. Ajouter ItemArray(n) = PointerToItem# O (1)

Retirer

  1. For i = 0 to n, trouve iHashArray(i) = Key.GetHash # O (log n) (tableau trié)
  2. Supprimer HashArray(i)# O (n) (tableau trié)
  3. Supprimer KeyArray(i)# O (1)
  4. Supprimer ItemArray(i)# O (1)

Obtenir l'article

  1. For i = 0 to n, trouve iHashArray(i) = Key.GetHash# O (log n) (tableau trié)
  2. Revenir ItemArray(i)

Boucle à travers

  1. For i = 0 to n, revenir ItemArray(i)

SortedDictionary

Mémoire

KeyArray(n) = non-sorted array<pointer>
ItemArray(n) = non-sorted array<pointer>
OrderArray(n) = sorted array<pointer>

Ajouter

  1. Ajouter KeyArray(n) = PointerToKey# O (1)
  2. Ajouter ItemArray(n) = PointerToItem# O (1)
  3. For i = 0 to n, trouve iKeyArray(i-1) < Key < KeyArray(i)(en utilisant ICompare) # O (n)
  4. Ajouter OrderArray(i) = n # O (n) (tableau trié)

Retirer

  1. For i = 0 to n, trouver iKeyArray(i).GetHash = Key.GetHash# O (n)
  2. Supprimer KeyArray(SortArray(i))# O (n)
  3. Retirer ItemArray(SortArray(i))# O (n)
  4. Supprimer OrderArray(i)# O (n) (tableau trié)

Obtenir l'article

  1. For i = 0 to n, trouver iKeyArray(i).GetHash = Key.GetHash# O (n)
  2. Revenir ItemArray(i)

Boucle à travers

  1. For i = 0 to n, revenir ItemArray(OrderArray(i))

SortedList

Mémoire

KeyArray(n) = sorted array<pointer>
ItemArray(n) = sorted array<pointer>

Ajouter

  1. For i = 0 to n, trouve iKeyArray(i-1) < Key < KeyArray(i) (en utilisant ICompare) # O (log n)
  2. Ajouter KeyArray(i) = PointerToKey# O (n)
  3. Ajouter ItemArray(i) = PointerToItem# O (n)

Retirer

  1. For i = 0 to n, trouver iKeyArray(i).GetHash = Key.GetHash# O (log n)
  2. Retirer KeyArray(i)# O (n)
  3. Supprimer ItemArray(i)# O (n)

Obtenir l'article

  1. For i = 0 to n, trouve iKeyArray(i).GetHash = Key.GetHash# O (log n)
  2. Revenir ItemArray(i)

Boucle à travers

  1. For i = 0 to n, revenir ItemArray(i)
Ama
la source
0

En essayant d'attribuer un score de performance à chaque cas présenté par @Lev, j'ai utilisé les valeurs suivantes:

  • O (1) = 3
  • O (log n) = 2
  • O (n) = 1
  • O (1) ou O (n) = 2
  • O (log n) ou O (n) = 1,5

Les résultats sont (plus élevé = meilleur):

Dictionary:       12.0 
SortedDictionary:  9.0 
SortedList:        6.5

Bien entendu, chaque cas d'utilisation donnera plus de poids à certaines opérations.

Jaime
la source
1
En règle générale, le poids de O (log n) serait log (n) / log (2) (+1 chaque fois que n double) tandis que le poids de O (n) serait n. Votre pondération serait donc correcte pour des tailles allant jusqu'à 4. Tout ce qui dépasse verra votre ratio 2: 1 augmenter rapidement. Par exemple, si n = 100 alors vous devriez avoir O (log n) = 15. Suite à une pensée similaire, votre O (1) pèserait 100. Conclusion: O (n) perd la bataille assez rapidement. Si ce n'est pas le cas, cela signifie que votre baie est petite et que l'efficacité n'est pas un problème.
Ama