【问题标题】:Merging dictionaries with duplicate keys producing child dictionaries将具有重复键的字典合并生成子字典
【发布时间】:2017-01-26 12:03:08
【问题描述】:

我正在尝试合并两个包含 n 个嵌套字典的字典。合并的行为需要采用重复的键并创建一个字典作为它的值。例如,合并这两个字典:

Data_X: {
    Data_B: {
        Data_C: "C",
    }
}

Data_Z: {
    Data_B: {
        Data_F: "F",
    }
}

我希望这个合并结果:

Data_A: {
    Data_B: {
        Data_C: "C",
        Data_F: "F",
    }
}

似乎找不到任何解决方案,它不仅将“值”用于“F”或“C”,而是将下一个字典添加到“Data_B”的“值”属性中

到目前为止,这是我采用第一个“价值”而不是创建一个新价值的地方,但这并不完全正确:

Dictionary<string, object>[] dictionaries = new Dictionary<string, object>[]
{
    (Dictionary<string, object>)dictX, 
    (Dictionary<string, object>)dictZ
};

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

【问题讨论】:

    标签: c# linq dictionary


    【解决方案1】:

    类似,并且作为一种扩展方法。

    public static class DictionaryExtensions
    {
        public static IDictionary<TKey, List<TValue>> Merge<TKey, TValue>(
            this IDictionary<TKey, TValue> me,
            IDictionary<TKey, TValue> other
        )
        {
            var keys = me.Concat(other)
                .GroupBy(x => x.Key)
                .ToDictionary(
                    x => x.Key,
                    x => x.Select(z => z.Value).ToList()
                );
            return keys;
        }
    }
    

    用法:

    var dic1 = new Dictionary<string, string> { { "B", "C" }, { "A", "X" } };
    var dic2 = new Dictionary<string, string> { { "B", "F" }, { "D", "D" } };
    var dicm = dic1.Merge(dic2);
    

    更新:

    使用这些扩展方法,您可以继续合并。

    public static IDictionary<TKey, List<TValue>> Merge<TKey, TValue>(this IDictionary<TKey, List<TValue>> me, IDictionary<TKey, List<TValue>> other)
    {
        var keys = me.Concat(other)
            .GroupBy(x => x.Key)
            .ToDictionary(
                x => x.Key,
                x => x.SelectMany(z => z.Value).ToList()
            );
        return keys;
    }
    
    public static IDictionary<TKey, List<TValue>> Merge<TKey, TValue>(this IDictionary<TKey, List<TValue>> me, IDictionary<TKey, TValue> other)
    {
        return me
            .Merge(
                other
                    .ToDictionary(
                        x => x.Key,
                        x => new List<TValue> { x.Value }
                    )
            );
    }
    

    用法:

    var dic1 = new Dictionary<string, string> { { "B", "C" }, { "A", "X" } };
    var dic2 = new Dictionary<string, string> { { "B", "F" }, { "D", "D" } };
    var dic3 = new Dictionary<string, string> { { "B", "F" }, { "E", "D" } };
    var dic4 = new Dictionary<string, string> { { "X", "F" }, { "E", "D" } };
    var dicm = dic1.Merge(dic2).Merge(dic3).Merge(dic4);
    

    【讨论】:

    • 此解决方案将“值”转换为列表类型对象,这意味着您不能再次使用它来合并您已经合并的两个字典。尝试将现有值转换为字典会爆炸。
    • 嗯,结果显然必须保存多个值,因此您需要一些列表或数组或哈希集来保存这些多个值。
    • 它不必保存多个值。它必须在重复键下创建一个新的子字典。 IE。 “Data_B”的值最终应该包含一个字典,其中包含两个键,“Data_C”和“Data_F”。
    • 这个解决方案给出了编译错误:方法的类型参数不能从用法中推断出来。我正在转换字典,你知道我还要在哪里指定类型吗?
    • 我不认为这个解决方案是我所希望的。当它尝试合并两个重复键时,我需要它在“value”属性中创建一个包含两个值的新字典。
    【解决方案2】:

    你只需要一点老式的递归。这假设字典的结构是兼容的,并且不对尝试合并字典和字符串值进行错误检查。

        Dictionary<string, object> MergeDictionary(IEnumerable<Dictionary<string, object>> dicts)
        {
            var l = dicts.SelectMany(d => d).ToLookup(kv => kv.Key, kv => kv.Value);
            return l.ToDictionary(
                g => g.Key,
                g => g.Count() == 1
                    ? g.First()
                    : MergeDictionary(g.Cast<Dictionary<string, object>>()));
        }
    

    要对其进行测试,您可以运行它,它会返回您想要的结果。

        static void TestMergeDictionary()
        {
            var dbx = new Dictionary<string, object> { { "Data_C", "C" } };
            var dx = new Dictionary<string, object> { { "Data_B", dbx } };
    
            var dbz = new Dictionary<string, object> { { "Data_F", "F" } };
            var dz = new Dictionary<string, object> { { "Data_B", dbz } };
    
            var da = MergeDictionary(new[] { dx, dz });
        }
    

    【讨论】:

    • 你太棒了!这是一种享受,谢谢。我对 linq 函数中的递归有点困惑。我对linq真的很穷。如果您有时间解释一下该 linq 函数,我将不胜感激。
    • 我们使用 SelectMany 将所有字典键组合在一起,并变成查找以将(多个)值组合在一起。这将返回一系列 IGrouping 对象。
    • (我在评论时按了回车键,花了太长时间......) SelectMany 从所有字典中返回一系列键值对。然后我们使用 ToLookup 按 Key 对它们进行分组。然后我们只是使用 ToDictionary() 将 Lookup 转换回 Dictionary,每个键都是分组中的键。对于值,如果分组仅包含一个对象,那么就是它(字典或字符串)。如果分组包含多个对象,那么这些是需要合并在一起的字典,这是通过递归调用 MergeDictionary 来完成的。
    【解决方案3】:

    与其获取.First(),不如直接返回.ToList()?这将为您提供所有字典中的键及其所有关联值。

    var x = new Dictionary<string, object>();
    x.Add("B", "F");
    x.Add("A", "D");
    var y = new Dictionary<string, object>();
    y.Add("B", "G");
    Dictionary<string, object>[] dictionaries = new Dictionary<string, object>[]
    {
            x,
            y
    };
    
    var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.ToList()); /* here */
    

    返回(来自 C# 交互式 shell,所以格式化有点奇怪):

     Dictionary<string, List<object>>(2) { 
            { "B", List<object>(2) { "F", "G" } }, 
            { "A", List<object>(1) { "D" } } 
        }
    

    【讨论】:

    • 正如我在上面 Maaren 的回答中提到的,此解决方案将“值”转换为 List 类型对象,这意味着您不能再次使用它来合并您已经合并的两个字典。尝试将现有值转换为字典会爆炸。
    猜你喜欢
    • 2013-11-21
    • 2011-10-17
    • 1970-01-01
    • 2020-01-22
    • 2022-08-18
    • 1970-01-01
    • 2012-10-22
    • 2011-03-25
    • 1970-01-01
    相关资源
    最近更新 更多