J'ai des objets dans la liste, disons List<MyClass>
et MyClass a plusieurs propriétés. Je voudrais créer un index de la liste basé sur 3 propriétés de MyClass. Dans ce cas, 2 des propriétés sont des int et une propriété est une date / heure.
En gros, j'aimerais pouvoir faire quelque chose comme:
Dictionary< CompositeKey , MyClass > MyClassListIndex = Dictionary< CompositeKey , MyClass >();
//Populate dictionary with items from the List<MyClass> MyClassList
MyClass aMyClass = Dicitonary[(keyTripletHere)];
Je crée parfois plusieurs dictionnaires sur une liste pour indexer différentes propriétés des classes qu'elle contient. Je ne sais pas comment gérer au mieux les clés composites. J'ai envisagé de faire une somme de contrôle des trois valeurs mais cela risque de provoquer des collisions.
c#
dictionary
AaronLS
la source
la source
Réponses:
Vous devez utiliser des tuples. Ils sont équivalents à une classe CompositeKey, mais Equals () et GetHashCode () sont déjà implémentés pour vous.
Ou en utilisant System.Linq
À moins que vous n'ayez besoin de personnaliser le calcul du hachage, il est plus simple d'utiliser des tuples.
S'il y a beaucoup de propriétés que vous souhaitez inclure dans la clé composite, le nom du type Tuple peut devenir assez long, mais vous pouvez raccourcir le nom en créant votre propre classe dérivant de Tuple <...>.
** édité en 2017 **
Il existe une nouvelle option commençant par C # 7: les tuples de valeur . L'idée est la même, mais la syntaxe est différente, plus légère:
Le type
Tuple<int, bool, string>
devient(int, bool, string)
et la valeurTuple.Create(4, true, "t")
devient(4, true, "t")
.Avec les tuples de valeur, il devient également possible de nommer les éléments. Notez que les performances sont légèrement différentes, vous voudrez peut-être faire des analyses comparatives si elles comptent pour vous.
la source
KeyValuePair<K,V>
et d'autres structures ont une fonction de hachage par défaut qui est connue pour être mauvaise (voir stackoverflow.com/questions/3841602/ ... pour plus de détails).Tuple<>
cependant n'est pas un ValueType, et sa fonction de hachage par défaut utilisera au moins tous les champs. Cela étant dit, si le problème principal de votre code est les collisions, alors implémentez un optimiséGetHashCode()
qui convient à vos données.La meilleure façon de penser est de créer une structure CompositeKey et de vous assurer de remplacer les méthodes GetHashCode () et Equals () afin d'assurer la vitesse et la précision lorsque vous travaillez avec la collection:
Un article MSDN sur GetHashCode ():
http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx
la source
Que diriez-vous
Dictionary<int, Dictionary<int, Dictionary<DateTime, MyClass>>>
?Cela vous permettrait de faire:
la source
CompositeDictionary<TKey1, TKey2, TValue>
(etc.) qui hérite simplement deDictionary<TKey1, Dictionary<TKey2, TValue>>
(ou quel que soit le nombre de dictionnaires imbriqués requis. Sans implémenter le type entier à partir de zéro nous-mêmes (au lieu de tricher en utilisant dictionnaires ou types imbriqués pour contenir les clés) c'est le plus rapide que nous obtenions.Vous pouvez les stocker dans une structure et l'utiliser comme clé:
Lien pour obtenir le code de hachage: http://msdn.microsoft.com/en-us/library/system.valuetype.gethashcode.aspx
la source
Tuple
s donc c'est une bonne solution!Maintenant que VS2017 / C # 7 est sorti, la meilleure réponse est d'utiliser ValueTuple:
J'ai choisi de déclarer le dictionnaire avec un ValueTuple anonyme
(string, string, int)
. Mais j'aurais pu leur donner des noms(string name, string path, int id)
.Perfwise, le nouveau ValueTuple est plus rapide que Tuple à
GetHashCode
mais plus lent àEquals
. Je pense que vous devrez faire des expériences complètes de bout en bout pour déterminer lequel est vraiment le plus rapide pour votre scénario. Mais la gentillesse de bout en bout et la syntaxe du langage de ValueTuple le font gagner.la source
Deux approches viennent immédiatement à l'esprit:
Faites comme Kevin l'a suggéré et écrivez une structure qui vous servira de clé. Assurez-vous de faire implémenter cette structure
IEquatable<TKey>
et de remplacer ses méthodesEquals
etGetHashCode
*.Écrivez une classe qui utilise des dictionnaires imbriqués en interne. Quelque chose comme:
TripleKeyDictionary<TKey1, TKey2, TKey3, TValue>
... cette classe aurait à l' intérieur d' un membre de typeDictionary<TKey1, Dictionary<TKey2, Dictionary<TKey3, TValue>>>
et exposerait des méthodes telles quethis[TKey1 k1, TKey2 k2, TKey3 k3]
,ContainsKeys(TKey1 k1, TKey2 k2, TKey3 k3)
, etc.* Un mot sur la nécessité de surcharger la
Equals
méthode: s'il est vrai que laEquals
méthode pour une structure compare la valeur de chaque membre par défaut, elle le fait en utilisant la réflexion - qui implique intrinsèquement des coûts de performance - et n'est donc pas très implémentation appropriée pour quelque chose qui est censé être utilisé comme clé dans un dictionnaire (à mon avis, en tout cas). Selon la documentation MSDN surValueType.Equals
:la source
Si la clé fait partie de la classe, utilisez
KeyedCollection
.C'est un
Dictionary
endroit où la clé est dérivée de l'objet.Sous les couvertures, c'est le dictionnaire
. Pas besoin de répéter la clé dans le
Key
etValue
.Pourquoi prendre une chance la clé n'est pas la même dans le
Key
que leValue
.Pas besoin de dupliquer les mêmes informations en mémoire.
Classe KeyedCollection
Indexeur pour exposer la clé composite
Quant à l'utilisation du type valeur fpr, la clé Microsoft le déconseille spécifiquement.
ValueType.GetHashCode
Tuple
n'est techniquement pas un type valeur mais souffre du même symptôme (collisions de hachage) et n'est pas un bon candidat pour une clé.la source
HashSet<T>
appropriéeIEqualityComparer<T>
serait également une option. Btw, je pense que votre réponse attirera des votes si vous pouvez changer vos noms de classe et d'autres noms de membres :)Puis-je suggérer une alternative - un objet anonyme. C'est la même chose que nous utilisons dans la méthode GroupBy LINQ avec plusieurs clés.
Cela peut sembler étrange, mais j'ai comparé Tuple.GetHashCode et les nouvelles méthodes {a = 1, b = 2} .GetHashCode et les objets anonymes gagnent sur ma machine sur .NET 4.5.1:
Objet - 89,1732 ms pour 10000 appels en 1000 cycles
Tuple - 738,4475 ms pour 10000 appels en 1000 cycles
la source
dictionary[new { a = my_obj, b = 2 }]
le code de hachage résultant sera une combinaison de my_obj.GetHashCode et ((Int32) 2) .GetHashCode.Une autre solution à celles déjà mentionnées serait de stocker une sorte de liste de toutes les clés générées jusqu'à présent et lorsqu'un nouvel objet est généré, vous générez son hashcode (juste comme point de départ), vérifiez s'il est déjà dans la liste, s'il est, puis ajoutez-y une valeur aléatoire, etc. jusqu'à ce que vous ayez une clé unique, puis stockez cette clé dans l'objet lui-même et dans la liste et renvoyez-la comme clé à tout moment.
la source