2012-02-24 17 views
11

Mi chiedevo se in C# esistesse un tipo incorporato simile a "Dizionario", ma dove TKey e TValue dovevano essere unici.Tipo di dizionario C# con chiavi e valori univoci

Per esempio ::

d.Add(1, "1"); 
d.Add(2, "1"); // This would not be OK because "1" has already been used as a value. 

So che questo è una specie di esotico, ma sembra che da quando ci sono circa un miliardo di tipi di raccolta nella BCL potrebbe esistere. Qualche idea?

+3

Imposta la parte del valore della chiave. –

+3

Non esiste una classe di questo tipo in .NET Framework. Ma puoi facilmente costruirne uno su un dizionario e su un hash o due dizionari. – dtb

+1

@Robert Harvey: se lo fa non può più fare 'd [1]', cosa che sconfigge lo scopo del Dizionario. Potresti anche utilizzare HashSet <> –

risposta

13

Che ne dici di avere Dizionario e HashSet/dizionario secondario inverso: risolverà il problema e funzionerà meglio dei controlli sul singolo dizionario.

Qualcosa del genere, avvolto come classe:

HashSet<string> secondary = new HashSet<string>(/*StringComparer.InvariantCultureIgnoreCase*/); 
Dictionary<int, string>dictionary = new Dictionary<int, string>(); 
object syncer = new object(); 

public override void Add(int key, string value) 
{ 
    lock(syncer) 
    { 
    if(dictionary.ContainsKey(key)) 
    { 
     throw new Exception("Key already exists"); 
    } 

    if(secondary.Add(value) 
    { 
     throw new Exception("Value already exists"); 
    } 
    dictionary.Add(key, value); 
    } 
} 
+1

E poi avvolgi questo in una classe personalizzata – Jason

+0

@Jason Se ti serve di più 1 volta - sicuramente sì! –

+1

@OlegDok Se devi leggere il codice più di 1 volta - sicuramente sì! (E dovresti sempre aspettarti di dover leggere nuovamente questo codice un giorno.) –

0

C'è un progetto situato here che ha un tipo come questo. Si chiama PairDictionary e funziona piuttosto bene. Non è la migliore risposta, ma per chiunque abbia bisogno di quella classe personalizzata.

+0

Il 'PairDictionary "è una soluzione terribile. Funziona internamente con le liste e ogni operazione è un'operazione di tipo O (n) diversa dalla O (1) del dizionario _real_ --- [fonte] (http://curations.codeplex.com/SourceControl/ latest # Curations/PairDictionary.cs) – t3chb0t

+0

@ t3chb0t Ti stai concentrando sull'implementazione e non sull'API. Sì, non è il massimo, ma il suo utilizzo è l'importante. dato che fa parte di un codice open source, devi inviare una patch che migliori la funzionalità :) –

0

Ho risolto questo problema memorizzando i dati come Dictionary<TKey, HashSet<TValue>>. È possibile sostituire Hashset con un altro dizionario se si desidera un valore con 2 chiavi primarie.

Dictionary<int, HashSet<int>> _myUniquePairOfIntegerKeys; 
// OR 
Dictionary<string, Dictionary<string, bool>> _myUniquePairOfStringKeysWithABooleanValue; 
1

Per puposes interni ho scritto una BiDictionary. Non è a prova di proiettile perché non lo espongo all'utente, quindi funziona bene per me. Permette a me di ottenere o la chiave come ho bisogno di.

Il KeyPair<,> è necessario per poter implementare il IEnumerable<,> e quindi il metodo Add in modo da poter utilizzare l'inizializzatore dell'oggetto.

internal class KeyPair<TKey1, TKey2> 
{ 
    public TKey1 Key1 { get; set; } 
    public TKey2 Key2 { get; set; } 
} 

Questa è la classe principale, come un oggetto dinamico in modo che possiamo usare nomi chiave su di esso durante il recupero di valori:

internal class BiDictionary<TKey1, TKey2> : DynamicObject, IEnumerable<KeyPair<TKey1, TKey2>> 
{ 
    private readonly Dictionary<TKey1, TKey2> _K1K2 = new Dictionary<TKey1, TKey2>(); 
    private readonly Dictionary<TKey2, TKey1> _K2K1 = new Dictionary<TKey2, TKey1>(); 

    private readonly string _key1Name; 
    private readonly string _key2Name; 

    public BiDictionary(string key1Name, string key2Name) 
    { 
     _key1Name = key1Name; 
     _key2Name = key2Name; 
    } 

    public override bool TryGetMember(GetMemberBinder binder, out object result) 
    { 
     if (binder.Name == _key1Name) 
     { 
      result = _K1K2; 
      return true; 
     } 

     if (binder.Name == _key2Name) 
     { 
      result = _K2K1; 
      return true; 
     } 

     result = null; 
     return false; 
    } 

    public void Add(TKey1 key1, TKey2 key2) 
    { 
     _K1K2.Add(key1, key2); 
     _K2K1.Add(key2, key1); 
    } 

    public IEnumerator<KeyPair<TKey1, TKey2>> GetEnumerator() 
    { 
     return _K1K2.Zip(_K2K1, (d1, d2) => new KeyPair<TKey1, TKey2> 
     { 
      Key1 = d1.Key, 
      Key2 = d2.Key 
     }).GetEnumerator(); 
    } 

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

Esempio:

dynamic bidic = new BiDictionary<string, string>("Key1", "Key2") 
{ 
    { "foo", "bar" }, 
    { "baz", "qux" } 
}; 
var bar = bidic.Key1["foo"]; 
var foo = bidic.Key2["bar"]; 

Essi possono andare fuori sincrono se si modifica uno qualsiasi dei dizionari esterni. A tale scopo, utilizzo ObservableDictionary in modo da poter aggiornare l'altro se si cambia ma, per semplicità, ho rimosso questa parte del codice per dimostrare la logica principale.

Problemi correlati