【问题标题】:Find item(s) in a nested dictionary by secondary key通过辅助键在嵌套字典中查找项目
【发布时间】:2021-11-22 16:32:32
【问题描述】:

这是我的问题:我有一个嵌套字典(主键: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是嵌套字典

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

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

也许你能帮帮我。 :)

【问题讨论】:

  • 所以它有效,但您想知道是否有更好的方法?我认为这是一次代码审查。
  • 是的,不是的。它是过程模拟的一部分。我每天有数千个订单,每个订单有 1-5 种产品。 (我们设计游戏 PC 并根据需要将它们拧在一起。)模拟应该尽可能快地运行,而且由于我们有时有非常复杂或大量的订单(例如游戏缺点),生产线也会延伸。因此,在检索结束时,完整的模拟(所有计算)可能只需要几毫秒。所以我尽量避免代码变慢。因此,我正在寻找最佳方式。

标签: c# dictionary nested


【解决方案1】:

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

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

示例 json 抛出异常,因为您的 productId (P1, P2) 不是唯一的且 P5 不存在。

如果我寻找 productId "P5" 我想得到... [{“字段”:“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);
}

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

无论如何,如果我的 cmets 有帮助,请点赞我的回答 :)

如果我是你,我会用真实数据制作一些测试用例。 不要忘记:如果您处于调试模式,请在项目中启用“优化代码”。在我的测试用例中,没有优化代码的 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}");

【讨论】:

  • 是的,你是对的。我没有注意不存在的键。所以我需要使用.SingleOrDefault().productDict.GetValueOrDefault()。 » 这是我的示例:Fiddle 第 54 行中的方法 GetByProductId()//这只是一个简单的例子,不是我所有课程的全部数量
  • 好的,这很好。我尝试使用 IEnumerable,但当时错过了 Linq 方法。所以我切换到字典和列表。但是 HashSet 可能是个好主意。我认为 PLINQ 很难阅读,但也许是更快的方法。 - 我会阅读并尝试提高我的技能。
猜你喜欢
  • 1970-01-01
  • 2011-05-13
  • 1970-01-01
  • 1970-01-01
  • 2013-01-19
  • 2011-02-01
  • 2020-09-02
相关资源
最近更新 更多