开发者

Reversing Dictionary using LINQ in C#

开发者 https://www.devze.com 2023-01-16 09:06 出处:网络
How to convert Dictioanry<String,List<String>> into Dictionary<String,String> i\'m having a dictionary like

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        开发者_JAVA百科          A
   b1                   B
   b2                  B
   c1                   C

is there any way to do this using LINQ

Thanks in advance


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.

Option 1: Throw on duplicates

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.)

Option 2: Skip duplicates

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).

Example program

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


// 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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消