首页 > 解决方案 > 通过辅助键在嵌套字典中查找项目

问题描述

这是我的问题:我有一个嵌套字典(主键:orderIds,辅助键:productIds)。

我的方案:

Dictionary<string, Dictionary<string, List<Task>>>

我需要查找productId并返回该值(Task对象)。

一个产品密钥通常不会出现在多个订单中。所以它是独一无二的。

这是一个json示例:

{
  "O1": {
    "P1": [
      {
        "Field": "V1"
      },
      {
        "Field": "V7"
      }
    ],
    "P2": [
      {
        "Field": "V2"
      },
      {
        "Field": "V8"
      }
    ]
  },
  "O2": {
    "P1": [
      {
        "Field": "V3"
      },
      {
        "Field": "V5"
      }
    ],
    "P2": [
      {
        "Field": "V4"
      },
      {
        "Field": "V6"
      }
    ]
  }
}

如果我寻找 productId "P5" 我想得到......

[{"Field":"V5"}]

我发现运行的唯一方法是......

return base.Values.Single(x => x.ContainsKey(productId))[productId];

// base是嵌套字典

但我不喜欢我在这里所做的。因为我从存在键的集合(值)中分离了正确的字典,最后我通过键过滤(集合 [键])仅获取值。

这基本上是两个步骤,但我怀疑有一种更简单的方法,只需一步。- 我就是找不到这个。

也许你能帮帮我。:)

标签: c#dictionarynested

解决方案


对于性能调优,请使用 HashSet 而不是 List。我也可以推荐 ISBN:0321637003(LINQ to Objects),也许有更新版本。但无论如何它仍然是非常好的内容。如果您的字典中有数百万个条目,您可以尝试 PLINQ。

返回 base.Values.Single(x => x.ContainsKey(productId))[productId];

Sample json 会引发异常,因为您的 productId (P1, P2) 不是唯一的且 P5 不存在。

如果我寻找 productId "P5" 我想得到... [{"Field":"V5"}] 没有 P5

我不确定它是否有帮助,但这是我的示例代码。我已将您的 json 放入一个文件并对其进行反序列化。

var lFile = new FileInfo(@"C:\_test\data.json");
using var lReader = lFile.OpenText();
var lJsonStr = lReader.ReadToEnd();
var lDicDic = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, List<ProductId>>>>(lJsonStr);

//var lTest1 = lDicDic.Values.Single(x => x.ContainsKey("P1"))["P1"]; //Not working, P1 is not unique!
var lTest2 = lDicDic["O2"]["P1"];
//contains  => "Field": "V3" + "Field": "V5"

var lTest3 = lDicDic
   .SelectMany(p => p.Value.Values.SelectMany(qItem => qItem))
   .FirstOrDefault(qProducts => qProducts.Field == "V5");
//contains  => "Field": "V5"

var lTest4 = lDicDic["O2"]["P1"].Last();
//contains  => "Field": "V5"

编辑:

在您的 Fiddler 代码之后,我能够对其进行测试。

public List<MyTask> GetByProductId(string productId)
{
    var lProductDict = Tasks.Values.SingleOrDefault(x => x.ContainsKey(productId));
    return lProductDict?.GetValueOrDefault(productId);
}

我的胃告诉我,你目前的方法很难改进;)如果你有很多关于这个嵌套字典的调用,并且它的变化不是很频繁,那么在开始时将字典从 TopDown 重构为 BottomUp 可能是有意义的处理。

无论如何,如果我的评论有帮助,请支持我的回答:)

如果我是你,我会用真实数据制作一些测试用例。不要忘记:如果您处于调试模式,请在项目中启用“优化代码”。在我的测试用例中,没有优化代码的 AVG 为 47ms,优化代码为 42ms: 在此处输入图像描述

你可以试试 PLINQ: https ://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/introduction-to-plinq

这是我的测试场景:

var lStopWatch = new Stopwatch();
lStopWatch.Restart();
var lJobs = new Jobs("F1");
const int TestOrderCount = 1000000;
const int TestAvgCount = 1000;
for (var lIndex = 1; lIndex < TestOrderCount; lIndex++)
{
    var lNoStr = lIndex.ToString("D6");
    lJobs.Add($"O{lNoStr}", $"P{lNoStr}", $"V{lNoStr}");
}

var GetTimes = new List<long>();
var lRandom = new Random();
var lTestCases = Enumerable
    .Range(1, TestAvgCount - 1)
    .Select(r => $"P{lRandom.Next(1, TestOrderCount - 1):D6}")
    .ToList();

var lSetupTimeMs = lStopWatch.ElapsedMilliseconds;
Debug.WriteLine($"SetupTimeMs: {lSetupTimeMs}");
foreach (var lTestCase in lTestCases)
{
    lStopWatch.Restart();
    var lTest = lJobs.GetByProductId(lTestCase);
    GetTimes.Add(lStopWatch.ElapsedMilliseconds);
}

var lAvg = GetTimes.Sum() / TestAvgCount; //AVG Ms per get
Debug.WriteLine($"AVG: {lAvg}");

推荐阅读