tags:

views:

4994

answers:

8

It's easy to get the value of a key from a .Net 2.0 generic Dictionary:

Dictionary<int, string> greek = new Dictionary<int, string>();
greek.Add(1, "Alpha");
greek.Add(2, "Beta");
string secondGreek = greek[2];  // Beta

But is there a simple way to get the key of a value?

int[] betaKeys = greek.WhatDoIPutHere("Beta");  // expecting single 2
A: 

revised: okay to have some kind of find you would need something other than dictionary, since if you think about it dictionary are one way keys. that is, the values might not be unique

that said it looks like you're using c#3.0 so you might not have to resort to looping and could use something like:

var key = (from k in yourDictionary where string.Compare(k.Value, "yourValue", true)  == 0 select k.Key).FirstOrDefault();
dove
Dictionary does not have .FindByValue. I would rather move to a different data structure than to loop through values.
Dour High Arch
+6  A: 

Dictionaries aren't really meant to work like this, because while uniqueness of keys is guaranteed, uniqueness of values isn't. So e.g. if you had

var greek = new Dictionary<int, string> { { 1, "Alpha" }, { 2, "Alpha" } };

What would you expect to get for greek.WhatDoIPutHere("Alpha")?

Therefore you can't expect something like this to be rolled into the framework. You'd need your own method for your own unique uses---do you want to return an array (or IEnumerable<T>)? Do you want to throw an exception if there are multiple keys with the given value? What about if there are none?

Personally I'd go for an enumerable, like so:

IEnumerable<TKey> KeysFromValue<TKey, TValue>(this Dictionary<TKey, TValue> dict, TValue val)
{
    if (dict == null)
    {
        throw new ArgumentNullException("dict");
    }
    return dict.Keys.Where(k => dict[k] == val);
}

var keys = greek.KeysFromValue("Beta");
int exceptionIfNotExactlyOne = greek.KeysFromValue("Beta").Single();
Domenic
An elegant solution, but this must work in 2.0. Duplicate values are unlikely but not impossible, returning a collection would be better.
Dour High Arch
+2  A: 

Maybe the easiest way to do it, without Linq, can be to loop over the pairs:

int betaKey; 
foreach (KeyValuePair<int, string> pair in lookup)
{
    if (pair.Value == value)
    {
        betaKey = pair.Key; // Found
        break;
    }
}
betaKey = -1; // Not found

If you had Linq, it could have done easily this way:

int betaKey = greek.SingleOrDefault(x => x.Value == "Beta").Key;
CMS
Unfortunately, this has to work on .Net 2.0
Dour High Arch
dour, but you have a var type above?! surely you're in 3.0? see my update below too.
dove
Apologies, I used "var" simply to reduce typing. I would prefer not to do a linear search, the dictionary could be large.
Dour High Arch
A: 
Cybis
That will merely switch what I am using for a key and only return the int value of the string key, I need to go both ways. And, as Domenic points out, I can have duplicate string values.
Dour High Arch
If you can have duplicate string values for your int keys, what do you expect to get back when you look-up by string? A list object of the corresponding int's?
Cybis
A: 

A dictionary doesn't keep an hash of the values, only the keys, so any search over it using a value is going to take at least linear time. Your best bet is to simply iterate over the elements in the dictionary and keep track of the matching keys or switch to a different data structure, perhaps maintain two dictionary mapping key->value and value->List_of_keys. If you do the latter you will trade storage for look up speed. It wouldn't take much to turn @Cybis example into such a data structure.

tvanfosson
+15  A: 

As everyone else has said, there's no mapping within a dictionary from value to key.

I've just noticed you wanted to map to from value to multiple keys - I'm leaving this solution here for the single value version, but I'll then add another answer for a multi-entry bidirectional map.

The normal approach to take here is to have two dictionaries - one mapping one way and one the other. Encapsulate them in a separate class, and work out what you want to do when you have duplicate key or value (e.g. throw an exception, overwrite the existing entry, or ignore the new entry). Personally I'd probably go for throwing an exception - it makes the success behaviour easier to define. Something like this:

using System;
using System.Collections.Generic;

class BiDictionary<TFirst, TSecond>
{
    IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
    IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

    public void Add(TFirst first, TSecond second)
    {
        if (firstToSecond.ContainsKey(first) ||
            secondToFirst.ContainsKey(second))
        {
            throw new ArgumentException("Duplicate first or second");
        }
        firstToSecond.Add(first, second);
        secondToFirst.Add(second, first);
    }

    public bool TryGetByFirst(TFirst first, out TSecond second)
    {
        return firstToSecond.TryGetValue(first, out second);
    }

    public bool TryGetBySecond(TSecond second, out TFirst first)
    {
        return secondToFirst.TryGetValue(second, out first);
    }
}

class Test
{
    static void Main()
    {
        BiDictionary<int, string> greek = new BiDictionary<int, string>();
        greek.Add(1, "Alpha");
        greek.Add(2, "Beta");
        int x;
        greek.TryGetBySecond("Beta", out x);
        Console.WriteLine(x);
    }
}
Jon Skeet
I'd like see this inherited from one of the base collections, so that it would benefit from the some of the built-in methods of that collection. Things like linq and IEnumerable support, etc.
Joel Coehoorn
I don't think there's any reason to make it derive from a concrete class - I don't like inheritance without very careful thought - but it could certainly implement IEnumerable etc. In fact, it could implement IDictionary<TFirst, TSecond> and IDictionary<TSecond, TFirst>.
Jon Skeet
(Although that would be quite weird if TFirst and TSecond were the same...)
Jon Skeet
+24  A: 

Okay, here's the multiple bidirectional version:

using System;
using System.Collections.Generic;
using System.Text;

class BiDictionary<TFirst, TSecond>
{
    IDictionary<TFirst, IList<TSecond>> firstToSecond = new Dictionary<TFirst, IList<TSecond>>();
    IDictionary<TSecond, IList<TFirst>> secondToFirst = new Dictionary<TSecond, IList<TFirst>>();

    private static IList<TFirst> EmptyFirstList = new TFirst[0];
    private static IList<TSecond> EmptySecondList = new TSecond[0];

    public void Add(TFirst first, TSecond second)
    {
        IList<TFirst> firsts;
        IList<TSecond> seconds;
        if (!firstToSecond.TryGetValue(first, out seconds))
        {
            seconds = new List<TSecond>();
            firstToSecond[first] = seconds;
        }
        if (!secondToFirst.TryGetValue(second, out firsts))
        {
            firsts = new List<TFirst>();
            secondToFirst[second] = firsts;
        }
        seconds.Add(second);
        firsts.Add(first);
    }

    // Note potential ambiguity using indexers (e.g. mapping from int to int)
    // Hence the methods as well...
    public IList<TSecond> this[TFirst first]
    {
        get { return GetByFirst(first); }
    }

    public IList<TFirst> this[TSecond second]
    {
        get { return GetBySecond(second); }
    }

    public IList<TSecond> GetByFirst(TFirst first)
    {
        IList<TSecond> list;
        if (!firstToSecond.TryGetValue(first, out list))
        {
            return EmptySecondList;
        }
        return new List<TSecond>(list); // Create a copy for sanity
    }

    public IList<TFirst> GetBySecond(TSecond second)
    {
        IList<TFirst> list;
        if (!secondToFirst.TryGetValue(second, out list))
        {
            return EmptyFirstList;
        }
        return new List<TFirst>(list); // Create a copy for sanity
    }
}

class Test
{
    static void Main()
    {
        BiDictionary<int, string> greek = new BiDictionary<int, string>();
        greek.Add(1, "Alpha");
        greek.Add(2, "Beta");
        greek.Add(5, "Beta");
        ShowEntries(greek, "Alpha");
        ShowEntries(greek, "Beta");
        ShowEntries(greek, "Gamma");
    }

    static void ShowEntries(BiDictionary<int, string> dict, string key)
    {
        IList<int> values = dict[key];
        StringBuilder builder = new StringBuilder();
        foreach (int value in values)
        {
            if (builder.Length != 0)
            {
                builder.Append(", ");
            }
            builder.Append(value);
        }
        Console.WriteLine("{0}: [{1}]", key, builder);
    }
}
Jon Skeet
Outstanding, Jon.
Dour High Arch
From what I read in msdn, shouldn't this be a BiLookup instead of a BiDictionary? Not that it is important or anything, just curious to if I understand things correctly here...
Svish
Also, I used GetByFirst and got back the EmptySecondList, added some things to it and then called GetByFirst again, wouldn't I get a list with some things in it and not an empty list then?
Svish
@Svish: No, because when you tried to add to the list it would throw an exception (you can't add to an array). And yes, BiLookup would probably be a better name.
Jon Skeet
+1  A: 

Dictionary class is not optimized for this case, but if you really wanted to do it (in C# 2.0), you can do:

public List<TKey> GetKeysFromValue<TKey, TVal>(Dictionary<TKey, TVal> dict, TVal val)
{
   List<TKey> ks = new List<TKey>();
   foreach(TKey k in dict.Keys)
   {
      if (dict[k] == val) { ks.Add(k); }
   }
   return ks;
}

I prefer the LINQ solution for elegance, but this is the 2.0 way.

dbkk