在C#中使用LINQ反转字典

时间:2010-09-13 05:24:22

标签: c# linq dictionary

如何转换

Dictioanry<String,List<String>> into Dictionary<String,String>

我有一本像

这样的词典
Dictioanry<String,List<String>>dictOne=new Dictionary<String,List<String>>();

哪个包含

Key(String)          Value(List<String>)

     A                a1,a2
     B                b1,b2
     C                c1

我需要将“dictOne”转换为

 Dictionary<String,String> dictReverse=new Dictionary<String,String>()

所以结果就像

Key(String)         Value(String)

   a1                  A
   a2                  A
   b1                   B
   b2                  B
   c1                   C

有没有办法使用LINQ

来做到这一点

提前致谢

3 个答案:

答案 0 :(得分: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!
};

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

选项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);

将此功能抽象为自己的方法的最通用的方法(想到)是实现一个扩展方法,为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);
}

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

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

选项2:跳过重复项

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

示例程序

以下是一个小测试程序,在GroupBy上显示选项1 ,其Dictionary<string, List<string>>没有重复元素

List<string>

输出:

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

答案 1 :(得分: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);

当然,原始字典中的每个键必须与一组唯一的值相关联,并且任何键都不能与也与其他键相关联的值相关联。

另请注意,Dictionary<K, V>未定义任何类型的枚举顺序。您可以使用Enumerable.OrderBy方法以适当的顺序枚举生成的字典。

答案 2 :(得分: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());

鉴于此输入词典:

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" } } };

结果将是:

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));
}

然后你可以像dictOne.Transpose(Enumerable.First)一样调用它来获取上述行为,dictOne.Transpose(Enumerable.Single)在有重复键(其他帖子的行为)时获取异常,dictOne.Transpose(Enumerable.Min)来选择第一个按字典顺序排列,或通过你自己的功能做任何你需要的事情。