Reputation: 14672
How to convert
Dictioanry<String,List<String>> into Dictionary<String,String>
i'm having a dictionary like
Dictioanry<String,List<String>>dictOne=new Dictionary<String,List<String>>();
and which containg
Key(String) Value(List<String>)
A a1,a2
B b1,b2
C c1
i need to convert the "dictOne" into
Dictionary<String,String> dictReverse=new Dictionary<String,String>()
So the result will be like
Key(String) Value(String)
a1 A
a2 A
b1 B
b2 B
c1 C
is there any way to do this using LINQ
Thanks in advance
Upvotes: 5
Views: 4964
Reputation: 128317
Update: As others have noted, in order for a dictionary to be truly "reversible" in this way, the values in your List<string>
objects need to all be unique; otherwise, you cannot create a Dictionary<string, string>
with an entry for every value in your source dictionary, as there would be duplicate keys.
Example:
var dictOne = new Dictionary<string, List<string>>
{
{ "A", new List<string> { "a1", "a2" } },
{ "B", new List<string> { "b1", "b2" } },
{ "C", new List<string> { "c1", "a2" } } // duplicate!
};
You have (at least) two options for dealing with this.
You may want to ensure that every element in every List<string>
is, in fact, unique. In this case, a simple SelectMany
with a ToDictionary
will accomplish what you need; the ToDictionary
call will throw an ArgumentException
on encountering a duplicate value:
var dictTwo = dictOne
.SelectMany(kvp => kvp.Value.Select(s => new { Key = s, Value = kvp.Key }))
.ToDictionary(x => x.Key, x => x.Value);
The most generic way (that comes to mind) to abstract this functionality into its own method would be to implement an extension method that does this for any IDictionary<T, TEnumerable>
implementation where TEnumerable
implements IEnumerable<TValue>
:
// Code uglified to fit within horizonal scroll area
public static Dictionary<T2, T1> ReverseDictionary<T1, T2, TEnumerable>(
this IDictionary<T1, TEnumerable> source) where TEnumerable : IEnumerable<T2>
{
return source
.SelectMany(e => e.Value.Select(s => new { Key = s, Value = e.Key }))
.ToDictionary(x => x.Key, x => x.Value);
}
The ugly proliferation of generic type parameters in the above method is to allow for types other than strictly Dictionary<T, List<T>>
: it could accept a Dictionary<int, string[]>
, for example, or a SortedList<string, Queue<DateTime>>
-- just a couple of arbitrary examples to demonstrate its flexibility.
(A test program illustrating this method is at the bottom of this answer.)
If duplicate elements in your List<string>
values is a realistic scenario that you want to be able to handle without throwing an exception, I suggest you take a look at Gabe's excellent answer for an approach that uses GroupBy
(actually, Gabe also provides a flexible approach that can cover either of these two cases based on a selector function; however, if you definitely want to throw on a duplicate, I'd still suggest the above approach, as it should be somewhat cheaper than using GroupBy
).
Here's a little test program illustrating Option 1 above on a Dictionary<string, List<string>>
with no duplicate elements in its List<string>
values:
var dictOne = new Dictionary<string, List<string>>
{
{ "A", new List<string> { "a1", "a2" } },
{ "B", new List<string> { "b1", "b2" } },
{ "C", new List<string> { "c1" } }
};
// Using ReverseDictionary implementation described above:
var dictTwo = dictOne.ReverseDictionary<string, string, List<string>>();
foreach (var entry in dictTwo)
{
Console.WriteLine("{0}: {1}", entry.Key, entry.Value);
}
Output:
a1: A a2: A b1: B b2: B c1: C
Upvotes: 14
Reputation: 86718
In the event that you would end up with duplicate keys in your result dictionary, you would have to pick a single one of those keys. Here's an implementation that just picks the first one it sees (using First
):
var dictReverse = (from kvp in dictOne
from value in kvp.Value
group kvp.Key by value)
.ToDictionary(grp => grp.Key, grp => grp.First());
Given this input dictionary:
var dictOne = new Dictionary<string, IEnumerable<string>> {
{ "C", new List<string> { "c1", "a2" } },
{ "B", new List<string> { "b1", "b2" } },
{ "A", new List<string> { "a1", "a2" } } };
The result would be:
c1: C a2: C b1: B b2: B a1: A
As Dan points out, you may want different behavior in the case of duplicate keys. You can create this function:
public static Dictionary<V, K> Transpose<K, V>(
this Dictionary<K, IEnumerable<V>> dictOne,
Func<IEnumerable<K>, K> selector)
{
return (from kvp in dictOne
from V value in kvp.Value
group kvp.Key by value)
.ToDictionary(grp => grp.Key, grp => selector(grp));
}
Then you could call it like dictOne.Transpose(Enumerable.First)
to get the above behavior, dictOne.Transpose(Enumerable.Single)
to get an exception when there's a duplicate key (the behavior of other posts), dictOne.Transpose(Enumerable.Min)
to pick the first one lexicographically, or pass in your own function do whatever you need.
Upvotes: 8
Reputation: 113402
// Associates each key with each of its values. Produces a sequence like:
// {A, a1}, {A, a2}, {B, b1}, {B, b2}, {C, c1}
var kvps = from kvp in dictOne
from value in kvp.Value
select new { Key = kvp.Key, Value = value };
// Turns the sequence into a dictionary, with the old 'Value' as the new 'Key'
var dictReverse = kvps.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
Of course, each key in the original dictionary must be associated with a unique set of values, and no key must be associated with values that are also associated with other keys.
Also bear in mind that Dictionary<K, V>
does not define any sort of enumeration order. You can use the Enumerable.OrderBy
method to enumerate the resulting dictionary in the appropriate order.
Upvotes: 9