【问题标题】:How can I use LINQ to avoid nested loops?如何使用 LINQ 避免嵌套循环?
【发布时间】:2011-11-28 20:29:50
【问题描述】:

我一直在阅读有关 LINQ to Objects 的内容,现在我的同事希望我将其介绍给他们。

现在,我对运算符和语法选择有了很好的了解,但我听说您可以通过使用 LINQ避免繁重的嵌套循环。不过,我很难想出一组好的“之前和之后”代码清单来证明这一点。

我在 Magennis 的书中找到了一个使用和不使用 LINQ 进行排序和分组的好例子,他也有一个编写 xml 的例子。但是那些嵌套循环呢?考虑到我们通常需要一个或两个 foreach 循环来遍历查询的结果,这甚至是一个现实的说法吗?

如果有人能向我解释这个想法(最好有具体的例子),我将不胜感激。

【问题讨论】:

  • 为什么以“没有建设性”的方式结束投票?似乎这个问题可能有广泛的用途,并允许提供一个或多个具体的答案。
  • @Henk:他要求具体的例子。也许可以改进标题以反映这一点。
  • 这个问题似乎很容易得出这样的答案,可以提高普通开发人员对如何充分利用 LINQ 的理解。我说:试试看。我们仍然可以在一天左右后关闭它。
  • @EricJ,yas4891,我不同意。 SO 是关于“这是我的问题,解决方案是什么”。然后,我们可以在任意多个中找出唯一的最佳解决方案。这个问题是“创建一个任意问题,然后是 Linq 有用的解决方案”。这不是一个有效的 SO 问题。我们为问题提供解决方案,而不是为需要解决的问题提供想法。这是一项民意调查,而不是一个只有最佳答案的问题。
  • @Anthony:他的措辞有点松散,但他的核心是在问“我如何使用 Linq 来避免严重嵌套的循环”。似乎足够具体,可以给出具体、有用的答案……他得到了。

标签: c# linq-to-objects


【解决方案1】:

假设您有很多产品,例如:

var products = new List<Product>
    {
        new Product { Id = 1, Category = "Electronics", Value = 15.0 },
        // etc.
    };

如果您想查找价值 > 100.0 美元的所有产品,按类别分组,您可以使用foreach

var results = new Dictionary<string, List<Product>>();

foreach (var p in products)
{
    if (p.value > 100.0)
    {
        List<Product> productsByGroup;

        if (!results.TryGetValue(p.Category, out productsByGroup))
        {
            productsByGroup = new List<Product>();
            results.Add(p.Category, productsByGroup);
        }
        productsByGroup.Add(p);
    }
}

或者,您可以简单地使用 LINQ 方法:

var results = products.Where(prod => prod.Value > 100.0)
                  .GroupBy(prod => prod.Category);

或者使用 LINQ 表达式语法:

var results = from p in products 
                  where p.Value > 100.0
                  group p by p.Category;

更简洁,更不容易出错。

【讨论】:

  • 谢谢,詹姆斯。我将您设置为我接受的答案,因为我认为它看起来非常现实,并且它在我正在寻找的东西上。
【解决方案2】:

这是一种可以使用 Linq 删除的嵌套循环。

foreach(SomeClass item in Items)
{
    foreach(SomeOtherClass subItem in item.SubItems)
    {
        // ...
    }
}

可以变成:

foreach(SomeOtherClass subItem in Items.SelectMany(i => i.SubItems))
{
}

使用the SelectMany extension method on IEnumerable

一个非常有用的地方是nested loop double-break scenarios

【讨论】:

    【解决方案3】:
    var results = new List<Object>();
    foreach(var i in list)
    {
        if (i.property == value)
        {
             foreach(var j in list.SubList)
             {
                  if (j.other == something)
                  {
                      results.push(j);
                  }
             }
        }
    }
    

    可能是:

    var results = list.Where(i => i == value)
                      .SelectMany(i => i.SubList)
                      .Where(j => j.other == something)
                      .ToList();
    

    【讨论】:

      【解决方案4】:

      这是一个有些人为的例子。

      假设给你一个字符串列表,你的任务是在 HashSet&lt;&gt; 中查找并返回在这些字符串中找到的所有控制字符。

      var listOStrings = new List<string> { ... };
      var result = new HashSet<char>();
      

      你可以这样做:

      foreach (var str in listOStrings)
      {
          foreach (var c in str)
          {
              if (Char.IsControl(c))
              {
                  result.Add(c);
              }
          }
      }
      

      或者使用 LINQ:

      result = new HashSet<char>(
          listOStrings
              .SelectMany(str => str.Where(Char.IsControl)));
      

      【讨论】:

      • 其实我看到很多人都是这样用Linq的,也许毕竟不是那么做作。
      【解决方案5】:

      最有用的示例是您可以使用 LINQ 中的内置方法,例如 AllAny。像这样:

      bool hasCats = listOfAnimals.Any(animal => animal.Type == "Cat");
      

      用带有 if 和 break 的 for 循环和一个 bool 检查变量来编写它,我想这至少需要五行代码来做同样的事情。嗯,让我们看看:

      bool hasCats = false;
      foreach(Animal animal in listOfAnimals)
      {
          if (animal.Type == "Cat")
          {
              hasCats = true;
              break;
          }
      }
      

      糟糕,9 行。而且你需要至少仔细阅读其中的三个才能知道代码的作用。

      嗯,差不多。假设哺乳动物有一个真实的类型层次结构。

      IEnumerable<Cat> allCats = listOfAnimals.OfType<Cat>();
      

      这会返回所有可以被铸造成Cat 的动物并返回它们,铸造并准备好使用。用循环写:

      List<Cat> allCats = new List<Cat>();
      foreach(var animal in listOfAnimals)
      {
          var cat = animal as Cat;
          if (cat != null)
          {
              allCats.Add(cat);
          }
      }
      

      说实话,您应该将其分解为一个单独的方法并使用 yield return cat; 来获得与 LINQ 版本相同的惰性行为。

      但我更喜欢查询语法。阅读起来很流畅,而且噪音很小。

      var cats = 
          from cat in listOfCats
          where cat.Age > 5
          where cat.Color == "White"
          select cat;
      

      用普通循环编写

      List<Cat> cats = new List<Cat>();
      foreach(Cat cat in listOfCats)
      {
          if (cat.Age > 5)
          {
              if (cat.Color == "White")
              {
                  cats.Add(cat);
              }
          }
      }
      

      同样需要使用 yield return 的单独方法来获得相同的惰性求值行为。

      【讨论】:

      • 谢谢,阿尔宾。您的答案缺少“之前”部分,这是我最初要求的。
      • @Skywise,我认为前面的部分写起来很简单,但我添加了它们。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-25
      • 2020-01-07
      • 2017-08-27
      • 2017-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多