A.R.
A.R.

Reputation: 15685

C# dictionary type with unique keys and values

I was wondering if there was a built in type in C# that was like 'Dictionary' but where both TKey and TValue had to be unique.

For example::

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

I know this is kind of exotic, but it seems that since there are about a billion collection types in the BCL it might exist. Any ideas?

Upvotes: 15

Views: 29434

Answers (5)

live627
live627

Reputation: 469

You could implement an extension method that skips adding to the dictionary if a given value already exists.

static class Extensions
{
    public static void AddSafe(this Dictionary<int, string> dictionary, int key, string value)
    {
        if (!dictionary.ContainsValue(value))
            dictionary.Add(key, value);
    }
}

Call it like a normal function

d.AddSafe(1, "1");
d.AddSafe(2, "1"); // This would not add anything

Upvotes: 1

t3chb0t
t3chb0t

Reputation: 18645

For internal puposes I wrote a BiDictionary. It isn't bullet-proof by I don't expose it to the user so it works fine for me. It allows to me get either key as I ofter need to.

The KeyPair<,> is necessary to be able to implement the IEnumerable<,> and thus the Add method so that we can use the object initializer.

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

This is the main class as a dynamic object so that we can use key names on it when retrieving values:

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();
    }
}

Example:

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

They may go out of sync if you modify any of the dictionaries outside. For this purpose I use ObservableDictionary so that I can update the other one if one changes but for the sake of simplicity I removed this part of the code to just demostrate the main logic.

Upvotes: 1

Jochem Geussens
Jochem Geussens

Reputation: 23

I solved this issue by storing the data as Dictionary<TKey, HashSet<TValue>>. You can replace the HashSet by another Dictionary if you want a value that has 2 primary keys.

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

Upvotes: 1

A.R.
A.R.

Reputation: 15685

There is a project located here that has a type like this. It is called PairDictionary and it works pretty well. Not the best answer, but for anyone who needs that custom class.

Upvotes: 0

Oleg Dok
Oleg Dok

Reputation: 21756

How about having Dictionary and HashSet/secondary reverse Dictionary - it will solve the issue and will perform better than checks on single Dictionary.

Something like this, wrapped as class:

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);
  }
}

Upvotes: 18

Related Questions