在C#中合并包含列表的字典

7
这与如何在C#中合并两个字典有点相关,可以参考这个问题。其中提出了一种优雅的Linq解决方案,非常棒。
然而,该问题涉及到Dictionary<Object1, Object2>,而我有一个值为List<Object2>的字典。
我正在寻找一种合并Dictionary<Object1, List<Object2>>的解决方案,并具备以下要求:
  • 如果Dictionary1包含与Dictionary2相同的键,则它们的List<Object2>列表应该被合并。您将得到一个新的键值对,具有共享的键和来自两个字典的组合列表。
  • 如果Dictionary1包含Dictionary2没有的键,则来自Dictionary1的List<Object2>列表应成为值,反之亦然。
这可能无法在Linq中实现,或者值得使用for循环等手动编写代码,但拥有一种优雅的解决方案会更好。
4个回答

3

处理合并关键冲突是一个难点。

如果我们先使用SelectMany将所有输入字典展开,就可以通过它们的键将元素分组在一起。

var result = dictionaries
    .SelectMany(dict => dict)
    .GroupBy(kvp => kvp.Key)

结果集包含多个组,每个组的键是来自原始字典的键,组的内容是一个相同键的IEnumerable<List<T>>。通过使用SelectSelectMany转换,我们可以将所有的List<T>合并成一个单独的IEnumerable<T>

var result = dictionaries
    .SelectMany(dict => dict)
    .GroupBy(kvp => kvp.Key)
    .Select(grp => new { Key = grp.Key, Items = grp.SelectMany(list => list)})

我们可以使用ToDictionary转换将其转换回List<T>,并从中获取一个字典。
var result = dictionaries
    .SelectMany(dict => dict)
    .GroupBy(kvp => kvp.Key)
    .Select(grp => new { Key = grp.Key, Items = grp.SelectMany(list => list)})
    .ToDictionary(kip => kip.Key, kip => new List<T>(kip.Items));

针对评论更新

您可以按照自己的喜好填充dictionaries。我假设它是一种实现了IEnumerable<IDictionary<TKey, List<T>>>接口的类型,其中TKeyT由您选择。

最简单的方法是使用List<T>,如下所示:

List<IDictionary<TKey, List<T>>> dictionaries 
    = new List<IDictionary<TKey, List<T>>>();

dictionaries.Add(dictionary1); // Your variable
dictionaries.Add(dictionary2); // Your variable

// Add any other dictionaries here.

// Code as above!

你如何填充字典 - Jonas Elfström
更新了我的答案,包括如何填充dictionaries变量。 - Paul Turner

3
我建议创建自己的扩展方法。这样更高效,也更容易修改。
public static void MergeDictionaries<OBJ1, OBJ2>(this IDictionary<OBJ1, List<OBJ2>> dict1, IDictionary<OBJ1, List<OBJ2>> dict2)
    {
        foreach (var kvp2 in dict2)
        {
            // If the dictionary already contains the key then merge them
            if (dict1.ContainsKey(kvp2.Key))
            {
                dict1[kvp2.Key].AddRange(kvp2.Value);
                continue;
            }
            dict1.Add(kvp2);
        }
    }

2
你只需要更改解决方案中的项目合并部分即可,对于对象,我们有以下内容:
.ToDictionary(group => group.Key, group => group.First())
即对于重复的项目,只需取第一个。 但我们可以使用以下方法:
.ToDictionary(group => group.Key, group => group.SelectMany(list => list).ToList());

连接列表的方法。

因此,最终表达式将是

var result = dictionaries.SelectMany(dict => dict)
            .ToLookup(pair => pair.Key, pair => pair.Value)
            .ToDictionary(group => group.Key, 
                          group => group.SelectMany(list => list).ToList());

如果你需要一些额外的列表合并逻辑(例如仅合并不同的项),可以尝试使用其他合并表达式。

1
我承认这并不是很美观,但对我来说很有效。
var d1 = new Dictionary<string, List<string>>();
var d2 = new Dictionary<string, List<string>>();

d1["test"] = new List<string>() { "Stockholm", "Motala" };
d1["more"] = new List<string>() { "numerous", "populous", "bigger", "plentiful" };
d2["test"] = new List<string>() { "Washington", "Charlottesville" };
d2["less"] = new List<string>() { "insufficient", "small", "imperceptible" };

var intersect = (from key in d1.Keys.Intersect(d2.Keys) select new { Key = key, Value = new List<string>(d1[key].Concat(d2[key])) }).ToDictionary(d => d.Key, d => d.Value);
var merged = d1.Concat(d2).Where(d => !intersect.Keys.Contains(d.Key)).Concat(intersect).ToDictionary(d => d.Key, d => d.Value);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接