【问题标题】:Count number of element in List<List<T>>计算 List<List<T>> 中的元素个数
【发布时间】:2011-06-01 12:16:31
【问题描述】:

我有一个List&lt;List&lt;T&gt;&gt;。 如何以最快的方式计算其中的所有元素,就好像它是单个 List&lt;T&gt; 一样?

到目前为止我已经使用了

List<int> result = listOfLists
  .SelectMany(list => list)
  .Distinct()
  .ToList().Count;

但这实际上是创建一个列表然后计算元素,这不是一个好主意。

【问题讨论】:

标签: c# linq


【解决方案1】:

通过使用 LINQ,我认为您的代码很好,不需要.ToList(),只需调用Count() 扩展,如下所示:

int result = listOfLists.SelectMany(list => list).Distinct().Count();

【讨论】:

  • 很可能比 tvanfosson 的回答慢一点。但它仍然是正确的并且更短/更容易阅读。
【解决方案2】:

如果您需要消除列表之间的重复项,我建议您使用带有 HashSet 的简单嵌套循环。它将 SelectMany 和 Distinct 操作组合到集合插入逻辑中,并且应该更快,因为 HashSet 具有 O(1) 查找时间。内部 Distinct() 可能实际上使用了类似的东西,但这完全省略了单个列表的构造。

var set = new HashSet<T>();
foreach (var list in listOfLists)
{
    foreach (var item in list)
    {
        set.Add(item);
    }
}
var result = set.Count;

【讨论】:

  • 它应该会快一点,因为一些委托调用被消除了。 Distinct 已经使用了具有几乎相同逻辑的 HashSet。而且我没有看到你在这里做SelectMany。所以你当前的代码是完全不同的。
  • @Homam -- 很好,它应该是一个嵌套循环。它基本上是相同的,除了不需要构建一个组合的内部列表来完成不同的操作。这样可以节省内存并可能会缩短时间。
  • 您可以省略Contains。如果项目已经存在,HashSet&lt;T&gt;.Add 不会抱怨。
  • @CodeInChaos - 我希望微软能保持一致,字典会抛出异常 - 但话又说回来,我猜在这种情况下你会插入一个重复的 key不是重复的item。
  • @Shurdoof 我很确定这会慢一些。这个答案的重点是它很快。为了获得良好的可读性,可以使用 Homam 的答案。
【解决方案3】:

要计算列表中所有列表中的所有元素,您可以使用聚合运算符:

int count = listOfLists.Sum(l => l.Distinct().Count());

【讨论】:

  • 这不会消除列表之间的重复,仅在每个列表中。
  • @lejon 为什么您接受与原始代码不同的答案? tvanfosson 和 Homam 的答案是正确的。
【解决方案4】:

我想有机会回答这个问题,只是为了强调什么时候应该使用 linq 以及什么时候应该使用经典 for。 不幸的是,今天人们并不关心性能,因为我们习惯于在非常强大的计算机上工作。不管怎样,只要试试下面的代码,你就会发现 Linq 比经典的 for 版本慢 100 倍以上。仅当您需要编写的表达式非常复杂并且希望使其更具可读性时,才应使用 Linq。 我没有花时间在下面介绍的解决方案上,因为我想专注于性能

public static void Main(string [] arg)
{
    //create the list
    List<List<string>> listOfList = new List<List<string>>()
                                      {
                                          new List<string>()
                                              {
                                                  "1.1","2.2"
                                              }
                                      ,
                                       new List<string>()
                                              {
                                                  "2.1","2.2","2.3"
                                              }
                                      };
    //stopwatch using Linq
    Stopwatch stopwatch=new Stopwatch();
    stopwatch.Start();

    int totalUsingLinq = listOfList.Sum(x => x.Count);

    stopwatch.Stop();
    Console.WriteLine("Using Linq:{0}",stopwatch.Elapsed); //00005713

    int totalUsingFor = 0;
    //stopwatch using classic for 
    stopwatch.Reset();
    stopwatch.Start();
    totalUsingFor = 0;
    for(int i=0;i<listOfList.Count;i++)
    {
       var mainItem = listOfList[i];
        if(mainItem!=null)
        {
            totalUsingFor += mainItem.Count;
        }
    }
    stopwatch.Stop();
    Console.WriteLine("Using for:{0}", stopwatch.Elapsed); //0000010

}

使用 for 的不同版本(仅作为示例)。 在这种情况下,我创建了一个非常“瓶颈”的功能,它可以做到与众不同,而且速度更快。

 public class Program
    {
      public static void Main(string[] arg)
        {
            //create the list
            List<List<string>> listOfList = new List<List<string>>()
                                      {
                                          new List<string>()
                                              {
                                                  "1.1","2.2","1.1","1.1","2.2","1.1","1.1","2.2","1.1","1.1"
                                              }
                                      ,
                                       new List<string>()
                                              {
                                                  "2.1","2.2","2.3","2.3","1.1","2.2","1.1","1.1","2.2","1.1","1.1","2.2","1.1","1.1","2.2","1.1","1.1","2.2","1.1"
                                              }
                                      };
            //stopwatch using Linq
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            int totalUsingLinq = listOfList.Sum(l => l.Distinct().Count());


            stopwatch.Stop();
            Console.WriteLine("Using Linq:{0}", stopwatch.Elapsed); //000012150    
            int totalUsingFor = 0;
            //stopwatch using classic for 
            stopwatch.Reset();
            stopwatch.Start();
            totalUsingFor = 0;
            for (int i = 0; i < listOfList.Count; i++)
            {
                var mainItem = listOfList[i];
                if (mainItem != null)
                {
                    for(int y=0;y<mainItem.Count;y++)
                    {
                      if(mainItem[y]!=null)
                      {
                          totalUsingFor++;
                          NullDuplicateItems(y, ref mainItem);
                      }   
                    }
                }
            }
            stopwatch.Stop();
            Console.WriteLine("Using for:{0}", stopwatch.Elapsed); //0009440
        }

        public static void NullDuplicateItems(int index,ref List<string > list)
        {
            var item = list[index];
            for(int i=index+1;i<list.Count;i++)
            {
                if(list[i]==item)
                {
                    list[i] = null;
                }
            }
        }

    }

【讨论】:

  • 您缺少 Distinct 功能,因此它是与 OP 问题完全不同的问题。并且这两个示例都可能需要几微秒,而您的示例中的列表如此之小。因此,除非它们是瓶颈,否则这是在经典循环上使用 linq 的一个示例。
  • 当我对它们中的每一个运行 100'000 次时,因子是 40,而不是 >100
  • 我没有像我说的那样花时间在代码上:即使你添加了 distinct,它至少会快 500 倍。在示例中,我没有创建一个巨大的列表,因为它只是一个示例,但如果想试一试,您可以再次尝试我的代码,生成一个包含数千个项目的列表,您将得到相同的结果
  • 这是正常的,系数是 40 而不是 >100,因为你运行它的机器是多任务的,所以它可能发生窗口在你的测试期间正在处理一些东西,所以你应该运行它很多时间并且然后计算平均值,但经典的 for 总是更快:40 倍!
  • 这就是为什么您以经典方式编写成为性能瓶颈的代码的原因。但如果不是,请不要打扰。 40 次几乎什么都没有。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-07
  • 1970-01-01
  • 1970-01-01
  • 2012-11-09
  • 2011-04-07
  • 2021-08-25
  • 2020-05-23
相关资源
最近更新 更多