3

Is it possible to force a Dictionary to have unique values? See the following example.

Dictionary<string, string> types = new Dictionary<string, string>()
{
        {"1", "one"},
        {"2", "two"},
        {"3", "three"}
};

In the event some one tried to execute the following line, they should receive an error.

types.Add("4","one");

I know this is not how a dictionary is built to operate and the correct answer may be to use a different/custom data structure.

JBone
  • 3,163
  • 11
  • 36
  • 47
  • 2
    Therefore, why are you not swapping your `keys` and `values` ....? Is there a reason why "one" (etc) wouldn't be an acceptable `key` here? – Arran Sep 16 '13 at 15:53
  • 2
    Use a different/custom data structure. – Dan Sep 16 '13 at 15:53
  • possible duplicate of [Getting key of value of a generic Dictionary?](http://stackoverflow.com/questions/255341/getting-key-of-value-of-a-generic-dictionary) – Tim S. Sep 16 '13 at 16:06
  • 1
    @TimS. That's not a duplicate at all... – Servy Sep 16 '13 at 16:13
  • @Servy well, the need for values to be unique just like keys are is reminiscent of a bi-directional dictionary. I thought it might solve his problem. – Tim S. Sep 16 '13 at 16:43
  • @TimS. You can have bi-directional dictionaries that support duplicate values (they just return a collection of keys) and even if it doesn't, it's still quite a bit more work to solve than problem than to solve this problem, so unless he actually does need to look up keys from a value, this isn't a duplicate. At best it's a related question that someone may derive insight from. Posting a link as a comment would be find; closing the question would not be. – Servy Sep 16 '13 at 16:45
  • I am not able to switch the keys/values in my real example although it seems like I could in this example. Both key and values would have to be individually unique in my implementation but the set I am dealing with here is generally small – JBone Sep 16 '13 at 17:04

4 Answers4

12

Keep two data structures; your regular dictionary and a HashSet<string> for the values. When you would like to add an item first check if the value is in the hash set. If it's not, then you know it's safe to add to both the dictionary and the set. (Also ensure you remove items from both collections on removal.)

If this is done in enough places then it may be worth creating your own IDictionary<K,V> implementation that uses both a regular Dictionary and a HashSet internally, so that you don't need to do so much work when using it. If this particular structure is only used in just a few places, it may not be worth the investment to create such a class.

Servy
  • 202,030
  • 26
  • 332
  • 449
6

You probably want to implement IDictionary and internally just call the corresponding Dictionary<TKey,TValue> methods. Also, you want a HashSet<TValue>. And then, on your Add method you would first check to see if your hashset.Contains(value). If it does, then you throw an exception.

On the other hand, do you really NEED this behavior? What if you just use a HashSet<Tuple<string,string>>. Then, any duplicates are just ignored. Or do you really NEED the data structure to throw an exception? If you don't, that's what I would go with.

Edit: good point @Alexei Levenkov. If you will have the same value with different keys, then the HashSet approach doesn't give you what you originally asked for. That would only be applicable if you were expecting the SAME key/value pairs.

aquinas
  • 23,318
  • 5
  • 58
  • 81
  • @Magnus You need a `Dictionary` and a `HashSet`. Tuples would only consider an item a duplicate if *both* key and value match, whereas the OP considers it a duplicate if *either* matches. – CodesInChaos Sep 16 '13 at 16:05
  • +1 on using `hashset.Contains(value)`. Note that `HashSet – Alexei Levenkov Sep 16 '13 at 16:06
  • 1
    The problem with a `HashSet` is that he may still need to be looking up a value for a key, and that's not possible. If he isn't using the data structure to actually look up the value of a key, then it may be the correct approach. – Servy Sep 16 '13 at 16:07
  • @CodesInChaos, yes, perhaps if O(1) lookup is needed for the key alone. – Magnus Sep 16 '13 at 16:07
4

Check for types.ContainsValue before adding

string str = "one";
if (!types.ContainsValue(str)) //doesn't add if its already there
{
    types.Add("4", str);
}
Venkata Krishna
  • 14,926
  • 5
  • 42
  • 56
3

unfortunately Dictionary provided by framework doesn't provide this feature. Fastest workaround would be build something like this

public class UniqueValueDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    public new void Add(TKey key, TValue value)
    {
        if (this.ContainsValue(value))
        {
            throw new ArgumentException("value already exist");
        }
        base.Add(key, value);
    }

    public new TValue this[TKey key]
    {
        get
        {
            return base[key];
        }
        set
        {
            if (this.ContainsValue(value))
            {
                throw new ArgumentException("value already exist");
            }

            base[key] = value;
        }
    }
}

Or something like the following(which is better in performance but not memory)

public class UniqueValueDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    Dictionary<TValue, TKey> valueAsKey = new Dictionary<TValue, TKey>();

    public new void Add(TKey key, TValue value)
    {
        if (valueAsKey.ContainsKey(value))
        {
            throw new ArgumentException("value already exist");
        }
        base.Add(key, value);
        valueAsKey.Add(value, key);
    }

    public new TValue this[TKey key]
    {
        get
        {
            return base[key];
        }
        set
        {
            if (valueAsKey.ContainsKey(value))
            {
                throw new ArgumentException("value already exist");
            }

            if (!this.ContainsKey(key))
            {
                this.Add(key, value);
            }
            else
            {
                base[key] = value;
                valueAsKey[value] = key;
            }
        }
    }

    //You may need to handle remove as well
}

Note:this will work only when you use UniqueValueDictionary<TKey, TValue> type, If you cast to Dictionary<TKey, TValue> you can add duplicate values.

As pointed in comments you can build something like this inheriting from IDictionary<TKey, TValue> not Dictionary<TKey, TValue> taking this as an idea.

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • 4
    That's the fastest to implement, but certainly not the fastest to execute. The `ContainsValue` check will be O(n). – Jim Mischel Sep 16 '13 at 16:03
  • Code with `ContainsValue` can't be "fastest" - maybe "easy to write" or "easy to understand" or "easy to support". – Alexei Levenkov Sep 16 '13 at 16:04
  • @JimMischel and @ Alexei Agree with you, this is just an idea. OP can make optimizations – Sriram Sakthivel Sep 16 '13 at 16:08
  • 1
    @SriramSakthivel Any optimization pretty much involves scrapping everything you have and starting over, so unless if he doesn't care about doing a linear search here the answer is fine, if he doesn't, it's not worth anything. – Servy Sep 16 '13 at 16:09
  • combine this with the hashset idea that others suggested and you're good to go. to solve the casting problem, have this implement IDictionary instead of subclass Dictionary... your interface implementation can then delegate everything (except this[] and Add) to an internal Dictionary object – Robert Levy Sep 16 '13 at 16:10
  • 1
    Also note that since the `Add` method isn't virtual you're just shadowing it, and that can be a serious problem. It means that whenever the object is in a variable of type `IDictionary` or `Dictionary` it won't do these checks; for these reasons you should encapsulate a dictionary, not extend it, as this can't reliably alter this behavior. – Servy Sep 16 '13 at 16:11