【问题标题】:Combine/Add dictionaries in a list when the values for two dictionary are equal in C#当两个字典的值在 C# 中相等时,在列表中组合/添加字典
【发布时间】:2013-07-09 17:32:36
【问题描述】:

我想从原始列表字典值 (List<Dictionary<String,Object>>) 中提取已处理的字典值列表 (List<Dictionary<String,Object>>)。

原始字典可能包含字符串/数值

例如:

Dictionary<String, Object> rawListDict = new Dictionary<String, Object>();
rawListDict.Add("Product","Apple");
rawListDict.Add("Region", "West");
rawListDict.Add("Profit", 90);

原始列表:

苹果西 90

苹果东 10 号

苹果西 80

已处理列表:

苹果西 170

苹果东 10 号

考虑一个包含具有相同产品和地区的字典的列表,我想要一个字典,当“产品”和“地区”相同时添加“利润”。 (即)具有相似项目的字典列表被分组为单个字典,没有任何重复

注意:原始列表可以超过 30K 条目。 :-(

我已经通过蛮力技术实现了一个逻辑,它消耗了大量的内存和时间。有没有办法以 LINQ 风格或任何其他方法来减少时间和内存?

编辑:我更喜欢字典,因为成员/键的数量仅在运行时才知道。

我已经实现的代码:

                    //Get fields which could be used for combining values
                    var nonMeasurableFields = report.datagrid_fields.
                        Where(field => field.dataType.Equals(ImFieldDatatype.STRING_VALUE) || field.dataType.Equals(ImFieldDatatype.DATE_VALUE)).
                        Select(field => field.name).ToList();

                    if (nonMeasurableFields != null && nonMeasurableFields.Count > 0)
                    {
                        #region Outer For Loop

                        for (int index = 0; index < processedData.Count; index++)
                        {
                            var baseDict = processedData.ElementAt(index);

                            Dictionary<String, Object> compareDict = null;

                            #region Recursive Loop

                            for (int recursiveIndex = index + 1; recursiveIndex < processedData.Count; recursiveIndex++)
                            {
                                compareDict = processedData.ElementAt(recursiveIndex);

                                int matchesCount = 0;

                                #region comparison logic

                                foreach (var key in nonMeasurableFields)
                                {
                                    var baseDictValue = baseDict[key];
                                    var compareDictValue = compareDict[key];

                                    if (baseDictValue == null && compareDictValue == null)
                                    {
                                        matchesCount++;
                                    }
                                    else
                                    {
                                        if (baseDictValue != null && compareDictValue == null)
                                        {
                                            matchesCount = 0;
                                        }
                                        else if (baseDictValue == null && compareDictValue != null)
                                        {
                                            matchesCount = 0;
                                        }
                                        else if (baseDictValue != null && compareDictValue != null)
                                        {
                                            if (baseDictValue.Equals(compareDictValue))
                                            {
                                                matchesCount++;
                                            }
                                            else
                                            {
                                                matchesCount = 0;
                                            }
                                        }

                                    }
                                }

                                #endregion

                                #region If Match -- Combine

                                if (matchesCount == nonMeasurableFields.Count)
                                {
                                    #region combine logic

                                    // Combine the two dictionary .. 

                                    processedData.Remove(baseDict);
                                    processedData.Remove(compareDict);

                                    // combine the base and compare dict

                                    Dictionary<String, Object> combinedDict = new Dictionary<string, object>();

                                    var keyNeededInDict = baseDict.Keys.ToList();

                                    foreach (var key in keyNeededInDict.ToList())
                                    {
                                        if (nonMeasurableFields.Contains(key))
                                        {
                                            combinedDict.Add(key, baseDict[key]);
                                        }
                                        else
                                        {
                                            Object value = Convert.ToDouble(baseDict[key]) + Convert.ToDouble(compareDict[key]);

                                            combinedDict.Add(key, value);
                                        }
                                    }

                                    processedData.Add(combinedDict);

                                    index = -1; // Resetting the looping index so that the merging works for all values
                                    recursiveIndex = -1; // Ensuring all the values are considered at least once whenever 
                                    // a change is made to the list (i.e merging the dict)
                                    break;
                                    #endregion
                                }
                                else
                                {
                                    // No matches
                                    // continue to next
                                }

                                #endregion
                            }

                            #endregion
                        }

                        #endregion
                    }

注意: 我将知道哪些键(键的值)是字符串类型和数字类型。 提供的示例仅用于演示目的。键和值仅在运行时才知道。 如果字符串值相等,我应该合并两个字典。我将在合并时添加数值。

编辑 2: 列表中的所有字典都将具有相同的键 不会丢弃任何值。具有相同值的字典将被合并。

【问题讨论】:

  • 不清楚您要达到的目标。请显示输入和预期输出。
  • 你有什么理由使用字典而不是正确的对象吗?
  • 我想要一个字典,为了确定,输出应该是一个字典还是一个列表? 我已经通过蛮力技术实现了一个逻辑,告诉我们你已经得到了什么。
  • @DanielHilgarth 我已经更新了问题并添加了示例。键仅在运行时才知道。我应该检查所有字符串值并组合数值
  • @AlexFilipovici 我想要通过添加具有相同字符串值的字典形成的具有唯一条目的字典列表

标签: c# dictionary


【解决方案1】:

所以,你有一个

IEnumerable<IDictionary<string, object>>

并且您想根据某些键集合并字典。

您现在需要字典的哪些键构成键集,以便对字典进行适当的分组。

您还需要一个委托函数来聚合每个非键集值。

在此基础上,您需要一个这样的函数来完成所有工作,

IEnumerable<IDictionary<string, object>> Merger(
        IEnumerable<IDictionary<string, object>> source,
        IEnumerable<string> keys,
        IDictionary<string, Func<IEnumerable<object>, object>> aggregators)
{
    var grouped = source.GroupBy(d => string.Join("|", keys.Select(k => d[k])));

    foreach(var g in grouped)
    {
        var result = new Dictionary<string, object>();
        var first = g.First();
        foreach(var key in keys)
        {
            result.Add(key, first[key]);
        }

        foreach(var a in aggregators)
        {
            result.Add(a.Key, a.Value(g.Select(i => i[a.Key])));
        }

        yield return result;
    }
}

因此,如果使用您的示例数据,您可以这样称呼它

var processedDictionaries = Merger(
    rawListDict,
    new[] { "Product", "Region" },
    new Dictionary<string, Func<IEnumerable<object>, object>>
        {
            { "Profit", objects => objects.Cast<int>().Sum() }
        });

如果你的值实际上是双精度的字符串表示,你可以像这样准备聚合器,

var aggregators = new Dictionary<string, Func<IEnumerable<object>, object>>();
aggregators.Add(
     "Profit",
     objects => objects.Cast<string>().Sum(s => double.Parse(s)));

【讨论】:

  • 感谢您的代码。就我而言,聚合器需要是动态的,而我在 Casting 方面遇到了问题。它不适用于 int、double/Double。还有一个补充是我可能一直没有聚合器。所以我将只保留一个字符串值相同的字典。通过使用合并,它也列出了重复的条目
  • @RajmohanKathiresan,我改变了分组功能,这应该有助于解决重复问题。您必须定义如何动态执行聚合。
【解决方案2】:

换句话说,您想要对一些字典键进行分组,并且您希望通过求和来聚合一个键。键是动态混合的。 (这听起来像是一个动态报告场景)。

var groupingKeys = new [] { "Product", "Region" };
var aggKey = "Profit";
List<Dictionary<String,Object>> rows = GetRows(); //provided

var results =
from r in rows
let groupingValues = groupingKeys.Select(key => r[key]).ToArray()
let groupingString = string.Join("|", groupingValues) //HACK - you better use an array comparer
let aggValue = Convert.ToInt32(r[aggKey])
group aggValue by groupingString into g
select new { Key = g.Key, Sum = g.Sum() }

希望对您有所帮助。它肯定包含错误,但您可能可以修复它们。

诀窍是首先从字典中提取分组键和值,然后使用标准 LINQ GroupBy 进行聚合。

【讨论】:

  • 效果很好。我将用数组比较器替换它。如果我的 aggKey 也是一个动态列表,我需要做什么。可能存在不存在任何 aggKey 值的情况,在这种情况下,处理后的列表只需要具有唯一条目(仅保留所有字符串值键的值相同的 dict 副本)
【解决方案3】:
var lookup = dicList.ToLookup(x => new{
                                    Product = x["Product"], 
                                    Region = x["Region"]});
var condensedDicList = lookup
       .Select(x => new Dictionary<string, object>(){
                         {"Product",x.Key.Product},
                         {"Region",x.Key.Region},
                         {"Profit",x.Sum(d=>(int)d["Profit"])}
        })
       .ToList();

但是说真的……为什么不写一个类呢

class MyData
{
    public string Product{get;set;}
    public string Region{get;set;}
    public int Profit{get;set;}
}

并为自己节省大量的球痛。

【讨论】:

  • 感谢您的样品。密钥仅在运行时已知。我不会对钥匙的数量有任何线索。例如。我可能是一个带有 N 个键的字典。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-05
  • 2018-01-20
  • 2022-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多