24

I have the following structure in my code Dictionary<TKeys, TValues> data;. I run some LINQ queries on both data types and often need to switch between Keys and Values. What is the best way to get the list of Keys for given Values and vice versa? Please, notice, that I usually have 'IEnumerable' and 'IEnumerable' as a result of my previous LINQ queries and would like to have something like IEnumerable<TKeys> Dictionary.GetAllKeys(IEnumerable<IValues> vals) and IEnumerable<TValues> Dictionary.GetAllValues(IEnumerable<IKeys> keys).

Maybe I need other data container for this task?

Regards, Alexander.

Alexander Galkin
  • 12,086
  • 12
  • 63
  • 115

3 Answers3

47
 var values = dictionary.Where(x => someKeys.Contains(x.Key)).Select(x => x.Value);
 var keys = dictionary.Where(x => someValues.Contains(x.Value)).Select(x => x.Key);
Bala R
  • 107,317
  • 23
  • 199
  • 210
  • Thank you for your answer, but your answer implies that I have a single "someKey" or "someValue". In my case I have them as `IEnumerable` or `IEnumerable`. – Alexander Galkin Jun 27 '11 at 19:06
  • @Alaudo I didn't realize that. In that case you will have to use `Contains()`. I have edited my answer. – Bala R Jun 27 '11 at 19:07
  • I editted my question to make it clearer. Your solution almost did it, I would prefer not to get the list of values, but rather a new dictionary as the outcome of the LINQ query. So, following your answer, I get `var newdict = dictionary.Where(x => someKeys.Contains(x.Key)).ToDictionary();` This last `ToDictionary()` does not work for me. – Alexander Galkin Jun 27 '11 at 19:24
  • 3
    @Alaudo Try `var newdict = dictionary.Where(x => someKeys.Contains(x.Key)).ToDictionary(x => x.Key, x=> x.Value);` – Bala R Jun 27 '11 at 19:27
11

A Dictionary<,> really isn't great for finding keys by value. You could write a bidirectional dictionary, as I have done in this answer, but it wouldn't necessarily be the best approach.

Of course you can use a dictionary as a sequence of key/value pairs, so you could have:

var keysForValues = dictionary.Where(pair => values.Contains(pair.Value))
                              .Select(pair => pair.Key);

Just be aware this will be an O(n) operation, even if your "values" is a HashSet or something similar (with an efficient containment check).

EDIT: If you don't really need a key/value relation - if it's more like they're just pairs - then using List<Tuple<Foo, Bar>> would make a certain amount of sense. The query ends up being the same, basically:

public IEnumerable<T1> GetAllFirst<T1, T2>(IEnumerable<Tuple<T1, T2>> source,
                                           IEnumerable<T2> seconds)
{
    HashSet<T2> secondsSet = new HashSet<T2>(seconds);
    return source.Where(pair => secondsSet.Contains(pair.Item2));
}

public IEnumerable<T2> GetAllSecond<T1, T2>(IEnumerable<Tuple<T1, T2>> source,
                                            IEnumerable<T1> firsts)
{
    HashSet<T1> firstsSet = new HashSet<T1>(firsts);
    return source.Where(pair => firstsSet.Contains(pair.Item1));
}
Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you for your prompt and quick answer! What I basically need is to 1:1 mapping between two generic lists, so that I can easily run queries against one list and get the directly mapped values from the second list for my next query. I was thinking about a list of tuples for this case. Is there any established practice in LINQ here? Regards,Alexander. – Alexander Galkin Jun 27 '11 at 19:15
  • Thank you a lot, I will consider this solution during my next major refactoring. I got an interesting solution for my question from @Bala R, which allows me to stick to Dictionary, for I have already implemented a bunch of queries against this structure. – Alexander Galkin Jun 27 '11 at 19:38
2

The best approach is to perform your linq query on the collection of key-value pairs, then use a Select projection to select either the Keys or the Values at the end of your query. This way there is no need to perform a look-up at the end of your query.

For example:

  Dictionary<string, string> data = new Dictionary<string, string>();
  // select all values for keys that contain the letter 'A'
  var values = data.Where(pair => pair.Key.Contains("A"))
                   .Select(pair => pair.Value);
ColinE
  • 68,894
  • 15
  • 164
  • 232
  • I editted my question to make it clearer that I have a list of values and keys, not just single values. Can I feed `IEnumerable<>` as an input for the `Contains()` predicate? – Alexander Galkin Jun 27 '11 at 19:14
  • I would definitely convert the selected value to a string and use firstordefault for safety. That way you have something in the value and it won't blow up. data.Where(pair => pair.Key.Contains("A")) .Select(pair => pair.Value.ToString()).FirstOrDefault(), – Phillip Holmes Jul 01 '20 at 16:58