【问题标题】:C# foreach looping and matching. Is there a faster way to do this?C# foreach 循环和匹配。有没有更快的方法来做到这一点?
【发布时间】:2021-06-01 12:06:12
【问题描述】:

我需要将一个列表中的值匹配到另一个列表。
两者都是列表。两者都有数十万条记录。
希望有人能告诉我一种方法来加快速度,因为它需要很长时间才能匹配。

foreach (var item in availabilityItems)
{
    foreach (var page in from page in pricePageList
        where item.Number == page.VendorItemNo
        where item.StartDate == page.SubSeasonStartDate
        where item.EndDate == page.SubSeasonEndDate
        select page)
    {
        item.VendorItemNo = page.VendorItemNo;
        item.PricePageNo = page.PricePageNo;
        item.NaItemNo = page.ItemNo;
    }
}

【问题讨论】:

  • 当你说记录时,你从哪里得到记录?是数据库吗?
  • AvailabilityItems 是来自 csv 的数据,PricePageList 是来自 SQL 查询的数据。 “记录”可能不是正确的术语。行可能?编辑:我注意到你使用导航。我使用术语记录,因为我也是 C/AL 开发人员。
  • 对数据进行排序肯定会产生巨大的影响,所以让我们从这个开始:数据是如何排序的?对于SQL,是否有能力更改ORDER BY 子句?
  • 我不认为 C/AL 是一门值得学习的语言,它经常激怒我,它只是一种必要的邪恶 :)
  • 恕我直言,将 csv 数据转储到表中,甚至可能直接通过批量插入流式传输数据。 itempage 是同一类型吗?或者项目表应该加入价格表,没有真正的 FK,这样你就可以简单地截断/替换价格。

标签: c# linq loops foreach


【解决方案1】:

这是一种防止多次列表扫描的简单算法。

如果两个列表都在Number/VendorItemNo 上排序,您可以使用 2 个索引变量自上而下扫描并将NumberVendorItemNo 进行比较。如果left 较大,则增加right 的索引。否则,如果right 更高,则增加左侧的索引。如果它们相等,您将获得匹配并增加两个索引。这背后的整个想法是您只扫描每个列表一次。它也可以用于多个列表。

例如两个按 id 排序的列表。

使用索引扫描它们:

1234 <-    1233 <-
1236       1234
1237       1235
1238       1238

比较一下,左边比右边高,右边增加。


1234 <-    1233
1236       1234 <-
1237       1235
1238       1238

我们有一场比赛。 (当然你有一些额外的标准要检查..)增加两者


1234       1233
1236 <-    1234
1237       1235 <-
1238       1238

左边高,右边增加


1234       1233
1236 <-    1234
1237       1235
1238       1238 <-

现在右边更高,所以向左增加


1234       1233
1236       1234
1237 <-    1235
1238       1238 <-

右还是高,左再增加。


1234       1233
1236       1234
1237       1235
1238 <-    1238 <-

我们找到了匹配...所以只有 1234 和 1238 匹配。

如果一个超出列表的计数,你就完成了。

【讨论】:

  • 这通常被称为merge join,不幸的是,在 LINQ 中还没有这方面的内容
  • 还没有,但您当然可以自己遍历枚举器来为您的特定数据实现算法。
  • @Charlieface 谢谢,我在多年前将 16 个带有事件的 midi 通道合并到 1 个单通道时就有了这个想法。太好了,它也是一个已知的算法。TIL:合并加入。
【解决方案2】:

假设pricePageList 包含关于三个字段组合的唯一元素,您可以构造一个包含页面的Dictionary&lt;TKey, Page&gt;,使用ValueTuple&lt;int, DateTime, DateTime&gt; 作为键。此类型开箱即用,因此您无需提供自定义 IEqualityComparer&lt;TKey&gt;

var pricePageDictionary = pricePageList
    .ToDictionary(p => (p.VendorItemNo, p.SubSeasonStartDate, p.SubSeasonEndDate));

foreach (var item in availabilityItems)
{
    var key = (item.Number, item.StartDate, item.EndDate);
    if (pricePageDictionary.TryGetValue(key, out var page))
    {
        item.VendorItemNo = page.VendorItemNo;
        item.PricePageNo = page.PricePageNo;
        item.NaItemNo = page.ItemNo;
    }
}

与嵌套循环方法相比,这应该非常快,因为在 Dictionary&lt;TKey, TValue&gt; 中搜索是一个 O(1) 操作。

【讨论】:

  • 我尝试使用它,但出现重复值错误。很难手动判断这么多记录中是否存在重复数据,但是我可以添加一个 if 语句来判断该键是否已经存在?
  • @NathanielDawg 因为pricePageList 没有唯一值,所以使用ToDictionary 运算符创建字典将失败。您可以手动创建字典,通过枚举pricePageList 并使用其set 索引器而不是Add 方法填充字典(保留最后一个重复项),或使用保留所有重复项的ToLookup 运算符重复项(每个键可以有多个值)。但你应该首先问问自己你想对这些案例做什么。对于每个键,哪个重复页面应该更新匹配项?
【解决方案3】:

如果它可以全部存储在内存中,您可以使用Dictionary 来存储项目以便快速查找。根据您的数据库,这将减少您的数据库的负载,无需在数据库端进行索引和排序,尤其是在您必须经常运行此过程的情况下。

代码可能更容易理解,虽然读取两个排序列表的另一个答案很聪明,但它可能需要更复杂的代码,并且还强制数据库执行排序,用完索引和额外的 IO。

显然,如果您有超过 1000 万条记录,这将开始出现故障,但在该记录范围内应该没问题。

Dictionary<string, PageItem> lookup = new Dictionary<string, PageItem>(StringComparer.OrdinalIgnoreCase);

批量加载示例

foreach (var item in pricePageList)
{
    lookup[item.VendorItemNo + "|" + item.SubSeasonStartDate + "|" + item.SubSeasonEndDate] = item;
}

示例搜索:

foreach (var item in availabilityItems)
{
    string key = item.Number + "|" + item.SubSeasonStartDate + "|" + item.SubSeasonEndDate;
    if (lookup.TryGetValue(key, out PageItem match))
    {
        item.Number = match.VendorItemNo;
        item.PricePageNo = match.PricePageNo;
        item.NaItemNo = match.ItemNo;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-08
    • 1970-01-01
    • 2014-04-13
    • 2012-02-12
    • 2017-02-17
    相关资源
    最近更新 更多