【问题标题】:Is it possible that a LINQ (to Object) query contains infinite loop?LINQ(对象)查询是否可能包含无限循环?
【发布时间】:2013-01-22 23:36:48
【问题描述】:

我有一个简单的课程:

public class RawBomItem
{
    private string material;
    private string item;
    private string component;
    private string quantity;
    private string b;
    private string spt;
...
}

每个数据成员都有一个属性。

然后我有一个包含此类实例的列表

    private List<RawBomItem> rawBom;

列表包含超过 70000 项。

此时我想在这个列表上运行一个有点复杂的 LINQ 查询。

List<string> endProducts = new List<string>(
    rawBom.Where(x1 => new List<string>(rawBom.Select(x2 => x2.Component)
                                              .Distinct())
                           .Contains(x1.Material) && (x1.B != "F"))
          .Select(x3 => x3.Material));

查询似乎陷入了无限循环。 (我等了几分钟才关机)

我会把它变成数据库来工作,我只是对可能是什么问题感兴趣。

【问题讨论】:

  • 为什么要一行行?
  • 好吧,尝试将列表长度减少到几百个,看看它是否仍然有效......
  • 目前还不清楚这个查询的作用,但考虑到它过滤了 70K 项。对于每一项,它最多处理 70K 项,每项最多两次。所以我们正在研究大约 5 到 10 次 十亿 次迭代。那应该需要一些时间。换句话说:O(N²) 不好。

标签: c# linq linq-to-objects


【解决方案1】:

我看不出应该如何存在无限循环,但您的代码效率极低。
对于rawBom 中的每个项目,您计算不同的组件集并将它们复制到一个新列表中。因此,如果列表中有 70,000 个项目,您将执行 70.000 ^ 2 = 4,900,000,000 次迭代。此外,对于列表中的每个项目,您都在再次迭代不同组件的列表。根据您拥有的不同组件的数量,您可以在顶部添加相同数量的迭代。

这可以改进:

var components = new HashSet<string>(rawBom.Select(x => x.Component).Distinct());
var endProducts = rawBom.Where(x => components.Contains(x.Material) &&
                                    x.B != "F")
                        .Select(x => x.Material)
                        .ToList();
  1. 我们从主查询中提取不同组件列表的创建,因此我们只需计算一次 - 而不是 70.000 次。
  2. 我们使用HashSet&lt;string&gt; 而不是List&lt;string&gt;。这会将对 Contains 的调用从 O(n) 更改为 O(1)

最终结果是您只列举了两次列表,结果只有 140,000 次迭代。现在将其与原始迭代次数进行比较。

【讨论】:

  • 我喜欢使用HashSet&lt;T&gt; 而不是List&lt;T&gt;。肯定是因为认识到这一点而 +1。
  • new Hashset() 不执行自己的Distinct(),无需自己执行?
【解决方案2】:

这是一个无限循环。除非你的序列是generator,但你这里没有。

你所拥有的是低效的代码。

rawBom.Where( // this goes over the 70,000 item collection once

对于这 70,000 个中的每一个:

new List<string>(rawBom.Select(x2 => x2.Component).Distinct()) // this goes over the 70,000 collection 3 times (first select, then distinct, then new list)
.Contains // another bunch of accesses depending on how many distinct items there were
.Select(x3 => x3.Material) // iterates over the resulting collection once

所以它会很慢,毫无疑问。

【讨论】:

    【解决方案3】:
    List<RawBomItem> list = new List<RawBomItem>();
    var components = list.Select(x => x.component).Distinct();
    var b = new Func<RawBomItem, bool>(x =>
      {
        return components.Contains(x.Material) && x.B != "F";
      });
    var v = list.Where(x => b(x)).Select(x1 => x1.material).ToList();
    

    【讨论】:

      【解决方案4】:

      我会稍微简化您的查询。我想这就是你想要的?

      List<string> endProducts = rawBom
      .Where(x1 => rawBom.Any(x2 => x2.Component == x1.Material) && x1.B != "2")
      .Select(x1 => x1.Material).ToList();
      

      这不会为 rawBom 中的每个项目创建任何额外的列表,但会自己使用它

      【讨论】:

      • 它仍然迭代接近 50 亿次。
      • 当然,使用 HashSet 要好得多。我写我的建议时还没有看到你的建议:)
      • 不应该是.Any(x2 =&gt; x2.Component == x1.Material)吗?原来是创建一个组件列表并查看列表是否包含材料,而不是它是否包含材料是子字符串的值。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-02-04
      • 1970-01-01
      • 2014-01-17
      • 2022-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多