在C#中使用LINQ反转字典

Tho*_*eld 5 c# linq dictionary

如何转换

Dictioanry<String,List<String>> into Dictionary<String,String>
Run Code Online (Sandbox Code Playgroud)

我有一本字典

Dictioanry<String,List<String>>dictOne=new Dictionary<String,List<String>>();
Run Code Online (Sandbox Code Playgroud)

哪个包含

Key(String)          Value(List<String>)

     A                a1,a2
     B                b1,b2
     C                c1
Run Code Online (Sandbox Code Playgroud)

我需要将"dictOne"转换成

 Dictionary<String,String> dictReverse=new Dictionary<String,String>()
Run Code Online (Sandbox Code Playgroud)

所以结果就像

Key(String)         Value(String)

   a1                  A
   a2                  A
   b1                   B
   b2                  B
   c1                   C
Run Code Online (Sandbox Code Playgroud)

有什么方法可以使用LINQ来做到这一点

提前致谢

Dan*_*Tao 13

更新:正如其他人指出,为了一本字典是真正的"逆转",在这样的价值观在你的List<string>对象需要的所有是唯一的; 否则,您无法Dictionary<string, string>为源词典中的每个值创建一个条目,因为会有重复的键.

例:

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!
};
Run Code Online (Sandbox Code Playgroud)

你有(至少)两个选项来处理这个问题.

选项1:重复投掷

您可能希望确保每个元素中的每个元素List<string>实际上都是唯一的.在这种情况下,一个简单的SelectMany用一个ToDictionary将完成你需要的东西; 该ToDictionary调用将ArgumentException在遇到重复值时抛出:

var dictTwo = dictOne
    .SelectMany(kvp => kvp.Value.Select(s => new { Key = s, Value = kvp.Key }))
    .ToDictionary(x => x.Key, x => x.Value);
Run Code Online (Sandbox Code Playgroud)

将此功能抽象为自己的方法的最通用的方法(想到)将实现一个扩展方法,为IDictionary<T, TEnumerable>实现的任何实现TEnumerable执行此操作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);
}
Run Code Online (Sandbox Code Playgroud)

上述方法中泛型类型参数的丑陋扩散是允许除严格以外的类型Dictionary<T, List<T>>:它可以接受Dictionary<int, string[]>例如或者SortedList<string, Queue<DateTime>>- 只是几个任意示例来证明其灵活性.

(说明此方法的测试程序位于此答案的底部.)

选项2:跳过重复

如果您的List<string>值中的重复元素是您希望能够在不抛出异常的情况下处理的现实场景,我建议您查看Gabe对使用方法的出色答案GroupBy(实际上,Gabe还提供了一种可以覆盖的灵活方法基于选择器函数的这两种情况中的任何一种;但是,如果你肯定想要复制,我仍然建议采用上述方法,因为它应该比使用它便宜一些GroupBy).

示例程序

下面是示出一个小的测试程序选项1上面上的Dictionary<string, List<string>>重复元素在它的List<string>值:

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);
}
Run Code Online (Sandbox Code Playgroud)

输出:

a1: A
a2: A
b1: B
b2: B
c1: C


Ani*_*Ani 9

// 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);
Run Code Online (Sandbox Code Playgroud)

当然,原始字典中的每个键必须与一组唯一值相关联,并且没有键必须与也与其他键相关联的值相关联.

还要记住,Dictionary<K, V>没有定义任何类型的枚举顺序.您可以使用该Enumerable.OrderBy方法以适当的顺序枚举生成的字典.


Gab*_*abe 7

如果您最终在结果字典中使用重复键,则必须选择其中一个键.这是一个实现,它只选择它看到的第一个(使用First):

var dictReverse = (from kvp in dictOne
                   from value in kvp.Value
                   group kvp.Key by value)
                   .ToDictionary(grp => grp.Key, grp => grp.First());
Run Code Online (Sandbox Code Playgroud)

鉴于此输入字典:

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" } } };
Run Code Online (Sandbox Code Playgroud)

结果将是:

c1: C
a2: C
b1: B
b2: B
a1: A

正如Dan指出的那样,在重复键的情况下,您可能需要不同的行为.您可以创建此功能:

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));
}
Run Code Online (Sandbox Code Playgroud)

然后你可以调用它dictOne.Transpose(Enumerable.First)来获得上述行为,dictOne.Transpose(Enumerable.Single)在有重复键(其他帖子的行为)时获取异常,dictOne.Transpose(Enumerable.Min)按字典顺序选择第一个,或者传入你自己的函数做你需要的任何事情.