9
votes

Dictionnaires multi-clés (d'un autre type) en C #?

bâtiment sur Cette question est une solution simple pour avoir un multi- Dictionnaire clé où soit clé individuellement em> peut être utilisé pour identifier la valeur?

IE. P>

MultikeyDictionary<TKey1, TKey2, TValue> foo;
foo.Add(key1, key2, value);
myValue = foo[key1];
// value == myValue
foo.Remove(key2);
myValue = foo[key1]; // invalid, Exception or null returned


0 commentaires

10 Réponses :


0
votes

Bien sûr, c'est une langue oo et vous pouvez mettre en œuvre tout ce que vous voulez. Vous allez avoir une certaine ambiguïté à résoudre (que si Thkey1 et Tkey2 sont le même type, quelles méthodes sont appelées alors?)


2 commentaires

Je ne sais en fait pas comment (ou si) le compilateur C # résolvait que ... Point intéressant.


Il s'agit d'une erreur de compilation: TYPE 'TEST ' définit déjà un membre appelé «X» avec les mêmes types de paramètres



10
votes

Ce blog post semble détailler une mise en œuvre assez décente.

Classe de dictionnaire générique multi-clés pour C #

multikeydicdiction est une classe C # qui enveloppe et étend le générique Objet de dictionnaire fourni par Microsoft in .NET 2.0 et supérieur. Cette permet à un développeur de créer un générique Dictionnaire des valeurs et référence la liste de valeurs via deux clés au lieu de juste celui fourni par Microsoft Mise en œuvre du générique Dictionnaire <...>. Vous pouvez voir mon Article sur CodeProject (ici), cependant Ce code est plus à jour et bug Gratuit.


4 commentaires

Non seulement que cette méthode ne compile pas lorsque K1 et K2 sont de même type, son support avec 3 dictionnaires - ce qui signifie que deux recherches sont nécessaires pour obtenir la valeur dans le pire des cas. La seule zone où cette approche est plus rapide est l'ajout.


Je ne sais pas ce que Nawfal parle, l'exemple ci-dessus est le plus rapide tout autour, mais voici une version mise à jour des tests de performance entre plusieurs "Multi-Key" implémentations de dictionnaire: ICI


@Jeffbridgman: Essayez ce lien (une cache de l'original): web.archive.org/web/20111209074636/http://www.aronweiler.com / ...


Le lien doit pointer ici: FyslexicDuck.com/2009 / 02 / ...



3
votes

Il n'y a rien d'intégré à la BCL .NET pour ce type de collection pour le moment.

Je vois deux options:

  1. Utilisez un dictionnaire à deux niveaux. Le premier niveau correspond à des clés différentes de certaines touches uniques courantes (disons un GUID) et le deuxième niveau correspond au GUID à la valeur réelle.

  2. Créez une classe de clés personnalisée et implémente des égaux () et GetHashCode () de sorte que tout composant de la clé soit suffisant pour trouver la totalité de la clé. Vous pouvez ensuite fournir des méthodes d'assistant pour construire des instances de la clé à l'aide d'une seule des valeurs afin que vous puissiez faire des recherches.


4 commentaires

1 est une idée vraiment intelligente. J'ai pensé à 2 mais je pense que vous atteindrez un mur de briques sur Gethashcode. Il aurait besoin de renvoyer la même valeur pour {1,1}, {2,1}, {1,2} mais pas {2,2} (bien que si la clé d'origine était {2,2}, il aurait besoin de. )


GetHashCode n'a pas besoin de renvoyer des valeurs uniques, mais les valeurs doivent être acceptées avec vos valeurs égales. Votre objet pourrait simplement retourner 0, économiser beaucoup de temps de calcul, mais gaspillera beaucoup lorsque la collection tente d'utiliser votre code de hachage (très) naïf.


@Ryan - Je voudrais implémenter gethashcode () à une hausse de l'une des valeurs. Les codes de hachage ne doivent pas nécessairement être uniques (comme Matthew identifie) - ils aident simplement que le dictionnaire minimise les collisions et affecter ainsi les performances.


1 n'est pas une bonne idée. Voir une discussion connexe ici Stackoverflow.com/a/1504548/661933 . Je veux dire des alternatives plus performantes y a-t-il



4
votes

Oui, définissez une classe qui ajoute l'objet à une hache interne avec les deux touches,

 public MyClass<k1, k2, T>: Dictionary<object, T>
  {
      private Dictionary<k1, k2> keyMap;
      public new Add(k1 key1Val, k2 key2Val, T object)
      {
         keyMap.Add(key1Val, key2Val);
         base.Add(k2, object)
      }
      public Remove(k1 key1Val) 
      { 
          base.Remove(keyMap[key1Val]); 
          keyMap.Remove(key1Val);
      }
      public Remove(k2 key2Val) 
      { 
        base.Remove(key2Val);
        keyMap.Remove(key2Val);
      }
  }


2 commentaires

(Mais comme indiqué, vous ne pouvez pas utiliser les mêmes types avec cette implémentation).


Le deuxième Suppression de la méthode n'est pas fonctionnel. Comment est le supprimer effectué sur keymap dont les touches sont k1 , mais vous fournissez k2 ? En bref, cette approche n'est pas complète.



0
votes

Vous ne serez pas en mesure de définir les surcharges pour les deux types et que le système Generics ne permet pas un nombre arbitraire de types (comme méthodes permettent des params). Donc, vous seriez coincé avec un ensemble de classes définies 2, 3, 4, etc. Touches simultanées. De plus, vous devez utiliser l'objet comme paramètre pour obtenir et définir, à l'aide de chèques de type d'exécution pour simuler la surcharge.

En outre, vous stockiez uniquement un dictionnaire de , les autres dictionnaires seraient de , et agirait comme index sur le dictionnaire principal.

C'est surtout du code de la plaque de la chaudière.


1 commentaires

Je n'ai jamais besoin de 2 types de clés. Ce n'est pas un problème.



0
votes

Vous pouvez trouver mon Indexmap Mise en œuvre pour être une bonne base de réécriture de Java dans C #. Le modèle de programmation n'est pas aussi élégant que je préférerais, mais cela n'est pas destiné à se développer avec directement. Il se situe plutôt derrière une bibliothèque de mise en cache qui fournit des annotations standard pour permettre un style de codage succinct. En utilisant l'interface de la carte, il fournit un modèle de composition propre lorsque vous y combinez avec des décorateurs de carte à auto-peuplement, expiration et évaspable. Je suis sûr que quelqu'un pouvait proposer une interface de programmation agréable pour une utilisation directe où il est acceptable de perdre l'avantage de l'interface de la carte.


0 commentaires

2
votes

Une autre implémentation simple (et efficace) serait d'utiliser PowerCollections ' Paire Type comme une clé de dictionnaire, quelque chose comme xxx

paire <> Implements est égal et gethashcode < / Code> Constamment, vous n'avez donc pas besoin de recourir à des dictionnaires à plusieurs niveaux (qui sont plus lourds et probablement moins efficaces).

Il y a aussi un Triple si vous avez besoin d'un dictionnaire 3 clés.


2 commentaires

Serais-je obtenir TVALUE si je fournis juste tkey2 ?


Cela ne répond pas à la question. La question demande un dictionnaire qui récupère des valeurs par la clé . Cette solution nécessiterait toujours les deux clés.



1
votes

J'ai essayé cela et cela fonctionne parfaitement (included ajouter, supprimer et indexer) xxx

et même je l'ai étendu à 4 valeurs: xxx

Profitez,

Ofir


1 commentaires

Comment cela répond-il à la question? De la question de OP: où soit la clé individuelle peut être utilisée pour identifier



2
votes

Je trouve de nombreuses réponses ici inutilement complexes, moins performantes ou simples inutilisables. La meilleure approche serait d'avoir un keyvaluepair code> de la clé secondaire et du lit de club de valeur ensemble comme valeur code> des dictionnaires. Cela vous permet d'avoir une seule recherche pour les opérations de retrait et de mise à jour. Une implémentation directe:

public class DualDictionary<TKey1, TKey2, TValue> : IEnumerable<KeyValuePair<Tuple<TKey1, TKey2>, TValue>>
{
    Dictionary<TKey1, KeyValuePair<TKey2, TValue>> _firstKeys;
    Dictionary<TKey2, KeyValuePair<TKey1, TValue>> _secondKeys;

    public int Count
    {
        get
        {
            if (_firstKeys.Count != _secondKeys.Count)
                throw new Exception("somewhere logic went wrong and your data got corrupt");

            return _firstKeys.Count;
        }
    }

    public ICollection<TKey1> Key1s
    {
        get { return _firstKeys.Keys; }
    }

    public ICollection<TKey2> Key2s
    {
        get { return _secondKeys.Keys; }
    }

    public IEnumerable<TValue> Values
    {
        get { return this.Select(kvp => kvp.Value); }
    }

    public DualDictionary(IEqualityComparer<TKey1> comparer1 = null, IEqualityComparer<TKey2> comparer2 = null)
    {
        _firstKeys = new Dictionary<TKey1, KeyValuePair<TKey2, TValue>>(comparer1);
        _secondKeys = new Dictionary<TKey2, KeyValuePair<TKey1, TValue>>(comparer2);
    }



    public bool ContainsKey1(TKey1 key)
    {
        return ContainsKey(key, _firstKeys);
    }

    private static bool ContainsKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
    {
        return dict.ContainsKey(key);
    }

    public bool ContainsKey2(TKey2 key)
    {
        return ContainsKey(key, _secondKeys);
    }

    public TValue GetValueByKey1(TKey1 key)
    {
        return GetValueByKey(key, _firstKeys);
    }

    private static TValue GetValueByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
    {
        return dict[key].Value;
    }

    public TValue GetValueByKey2(TKey2 key)
    {
        return GetValueByKey(key, _secondKeys);
    }

    public bool TryGetValueByKey1(TKey1 key, out TValue value)
    {
        return TryGetValueByKey(key, _firstKeys, out value);
    }

    private static bool TryGetValueByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, out TValue value)
    {
        KeyValuePair<T, TValue> otherPairing;
        bool b = TryGetValue(key, dict, out otherPairing);
        value = otherPairing.Value;
        return b;
    }

    private static bool TryGetValue<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict,
                                          out KeyValuePair<T, TValue> otherPairing)
    {
        return dict.TryGetValue(key, out otherPairing);
    }

    public bool TryGetValueByKey2(TKey2 key, out TValue value)
    {
        return TryGetValueByKey(key, _secondKeys, out value);
    }

    public bool Add(TKey1 key1, TKey2 key2, TValue value)
    {
        if (ContainsKey1(key1) || ContainsKey2(key2))   // very important
            return false;

        AddOrUpdate(key1, key2, value);
        return true;
    }

    // dont make this public; a dangerous method used cautiously in this class
    private void AddOrUpdate(TKey1 key1, TKey2 key2, TValue value)
    {
        _firstKeys[key1] = new KeyValuePair<TKey2, TValue>(key2, value);
        _secondKeys[key2] = new KeyValuePair<TKey1, TValue>(key1, value);
    }

    public bool UpdateKey1(TKey1 oldKey, TKey1 newKey)
    {
        return UpdateKey(oldKey, _firstKeys, newKey, (key1, key2, value) => AddOrUpdate(key1, key2, value));
    }

    private static bool UpdateKey<S, T>(S oldKey, Dictionary<S, KeyValuePair<T, TValue>> dict, S newKey,
                                        Action<S, T, TValue> updater)
    {
        KeyValuePair<T, TValue> otherPairing;
        if (!TryGetValue(oldKey, dict, out otherPairing) || ContainsKey(newKey, dict))
            return false;

        Remove(oldKey, dict);
        updater(newKey, otherPairing.Key, otherPairing.Value);
        return true;
    }

    public bool UpdateKey2(TKey2 oldKey, TKey2 newKey)
    {
        return UpdateKey(oldKey, _secondKeys, newKey, (key1, key2, value) => AddOrUpdate(key2, key1, value));
    }

    public bool UpdateByKey1(TKey1 key, TValue value)
    {
        return UpdateByKey(key, _firstKeys, (key1, key2) => AddOrUpdate(key1, key2, value));
    }

    private static bool UpdateByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, Action<S, T> updater)
    {
        KeyValuePair<T, TValue> otherPairing;
        if (!TryGetValue(key, dict, out otherPairing))
            return false;

        updater(key, otherPairing.Key);
        return true;
    }

    public bool UpdateByKey2(TKey2 key, TValue value)
    {
        return UpdateByKey(key, _secondKeys, (key1, key2) => AddOrUpdate(key2, key1, value));
    }

    public bool RemoveByKey1(TKey1 key)
    {
        return RemoveByKey(key, _firstKeys, _secondKeys);
    }

    private static bool RemoveByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> keyDict,
                                          Dictionary<T, KeyValuePair<S, TValue>> valueDict)
    {
        KeyValuePair<T, TValue> otherPairing;
        if (!TryGetValue(key, keyDict, out otherPairing))
            return false;

        if (!Remove(key, keyDict) || !Remove(otherPairing.Key, valueDict))
            throw new Exception("somewhere logic went wrong and your data got corrupt");

        return true;
    }

    private static bool Remove<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
    {
        return dict.Remove(key);
    }

    public bool RemoveByKey2(TKey2 key)
    {
        return RemoveByKey(key, _secondKeys, _firstKeys);
    }

    public void Clear()
    {
        _firstKeys.Clear();
        _secondKeys.Clear();
    }

    public IEnumerator<KeyValuePair<Tuple<TKey1, TKey2>, TValue>> GetEnumerator()
    {
        if (_firstKeys.Count != _secondKeys.Count)
            throw new Exception("somewhere logic went wrong and your data got corrupt");

        return _firstKeys.Select(kvp => new KeyValuePair<Tuple<TKey1, TKey2>, TValue>(Tuple.Create(kvp.Key, kvp.Value.Key),
                                                                                      kvp.Value.Value)).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}


0 commentaires

0
votes

Pas une solution directe et non viable pour les touches multi-clés, mais travaillé pour mon étui d'utilisation.

Dictionary<Guid, Object> objs = new Dictionary<Guid, Object>();
Dictionary<int, Guid> guids = new Dictionary<int, Guid>();

private void Set(object sender, Object obj)
{
    objs[obj.Guid] = obj;
    guids[obj.Id] = obj.Guid;
}

public Object Get(int id)
{
    return guids.ContainsKey(id) ? Get(guids[id]) : null;
}

public Object Get(Guid guid)
{
    return objs.ContainsKey(guid) ? objs[guid] : null;
}


0 commentaires