C#将一个列表与其他列表的一部分进行比较

时间:2016-10-07 13:36:20

标签: c# performance linq loops

我正在尝试从网站上删除不需要的图片。产品图像文件夹包含超过200000个图像。我有一个在列表中处于非活动状态的产品代码列表。我有另一个列表中的文件名列表。

List<string> lFileList = files.ToList();
List<string> lNotinfiles = new List<string>();
foreach (var s in lFileList)
{
   var s2 = (from s3 in lProductsList 
             where s.Contains(s3.cProductCode) 
             select s3.cProductCode).FirstOrDefault();
   if (s2 == null)
   {
      lNotinfiles.Add(s);
   }
}

此处lProductsList是包含未使用的ProductCodes的列表。 图像列表包含同一产品的多个图像,但图像名称包含产品代码(主要是它开头,可能有_1,_2.jpg将在那里。

以上代码可以正常工作,但单个文件夹获取Not in列表需要5分钟以上。我确实尝试了以下但是花了超过15分钟。

var s2 = (from s3 in lProductsList 
          where s.IndexOf(s3.cProductCode) >= 0 
          select s3.cProductCode).FirstOrDefault();

我试图将所有循环一起删除也无效。

什么应该是更快实现这一目标的最佳方法。

3 个答案:

答案 0 :(得分:2)

我建议:使用HashSet,等待ToList,也许GroupBy

  

HashSet +使用ToList

目前你的代码的时间复杂度为o(n)2 - 你迭代外部列表,每个项目迭代内部列表的所有项目。

lProductsList的类型从列表更改为包含HashSet<string>的代码。查找HashSet中的项目是o(1)(列表是o(n))。然后当你迭代lFileList的每一次以查找它们是否在lProductsList时,它将是o(n)的时间复杂度而不是o(n)2。

此代码将显示使用2个列表或使用列表与HashSet之间的时差:

var items = (new[] { "1", "2", "3","4","5","6","7","8","9","10" }).SelectMany(x => Enumerable.Repeat(x, 10000)).ToList();
var itemsToFilterOut = new List<string> { "1", "2", "3" };

var efficientItemsToFilterOut = new HashSet<string>(itemsToFilterOut);

var watch = System.Diagnostics.Stopwatch.StartNew();
var unwantedItems = items.Where(item => itemsToFilterOut.Contains(item)).ToList();
watch.Stop();
Console.WriteLine(watch.TotalMilliseconds);

watch = Stopwatch.StartNew();
var efficientUnwantedItems = items.Where(item => efficientItemsToFilterOut.Contains(item)).ToList();
watch.Stop();
Console.WriteLine(watch.TotalMilliseconds);

至于将其放在代码的上下文中:

var notInUseItems = new HashSet(from item in lProductsList
                                select item.cProductCode);

//Notice that here I am not using the materialized `lFileList`
lNotinfiles = files.Where(item => !notInUseItems.Contains(item));
  

的GroupBy

此外 - 您说该列表包含映射到同一个键的多个项目。在过滤之前使用GroupBy。检查此添加的性能:

watch = Stopwatch.StartNew();
var moreEfficientUnwantedItems = items.GroupBy(item => item)
     .Where(group => efficientItemsToFilterOut.Contains(group.Key))
     .Select(group => group.Key);
watch.Stop();
Console.WriteLine(watch.TotalMilliseconds);

检查您的数据,以分析重复数量的重要程度,并在需要时使用GroupBy

答案 1 :(得分:1)

两个建议:

  1. 不要具体化文件.ToList(),即不要等到检索所有文件
  2. NotInFiles组织为HashSet<String>以获得更好的合并O(1)而不是O(N)
  3. 这样的事情:

      //TODO: you have to implement this 
      prtivate static String ExtractProductCode(string fileName) {
        int p = fileName.IndexOf('_');
    
        if (p >= 0)
          return fileName.SubString(0, p);
        else
          return fileName;  
      }
    

    ...

      HashSet<String> NotInFiles = new HashSet<String>(
        lNotinfiles, 
        StringComparer.OrdinalIgnoreCase); // file names are case insensitive
    

    ...

      var files = Directory 
        .EnumerateFiles(@"C:\MyPictures", "*.jpeg", SearchOption.AllDirectories)
        .Where(path => Path.GetFileNameWithoutExtension(path))
        .Select(path => ExtractProductCode(path))
        .Where(code => !NotInFiles.Contains(code))
        .ToList(); // if you want List materialization
    

答案 2 :(得分:0)

您正在将您的(我假设)数组转换为List,然后执行foreach 直接在数组上使用for应该会使它至少快一点。

List<string> lNotinfiles = new List<string>();
            for(int i = 0; i < files.Count(); i++)
            foreach (var s in files)
             {
                var s2 = (from s3 in lProductsList where s.Contains(s3.cProductCode) select s3.cProductCode).FirstOrDefault();
                if (s2 == null)
                   {
                      lNotinfiles.Add(s);
                   }
             }