如何转换
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
来做到这一点提前致谢
答案 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!
};
你有(至少)两个选项来处理这个问题。
您可能希望确保每个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>>
- 只是几个任意的例子来证明它的灵活性。
(说明此方法的测试程序位于此答案的底部。)
如果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)
来选择第一个按字典顺序排列,或通过你自己的功能做任何你需要的事情。