如何快速计算字符串在字符串列表的给定部分中出现的频率?

本文关键字:字符串 频率 计算 何快速 列表 定部 | 更新日期: 2023-09-27 18:14:34

我有一个字符串列表,我需要计算其中出现特定字符串的列表条目的数量(并且整个列表仅用于列表的子集而不是整个列表)。

下面的代码工作得很好,但是它的性能是.....遗憾的是,这不是一个可接受的niveau,因为我需要解析500k到900k个列表条目。对于这些条目,我需要运行下面的代码大约10k次(因为我需要分析列表的10k部分)。这需要177秒甚至更长时间。所以我的问题是,我如何才能做到这一点……快?

private int ExtraktNumbers(List<string> myList, int start, int end)
{
    return myList.Where((x, index) => index >= start && index <= end 
                        && x.Contains("MYNUMBER:")).Count();
}

如何快速计算字符串在字符串列表的给定部分中出现的频率?

现在我们知道你调用了这个方法10,000次,这是我的建议。我假设你已经硬编码了"号码:",这意味着你在每个电话都做不同的范围?如果是这样的话…

首先,运行一个'indexing'方法并创建一个索引匹配的列表。然后你可以很容易地计算出你需要的匹配范围。

注意:这是一些快速的,你甚至可以进一步优化它:

List<int> matchIndex = new List<int>();
void RunIndex(List<string> myList)
{
    for(int i = 0; i < myList.Count; i++)
    {
        if(myList[i].Contains("MYNUMBER:"))
        {
            matchIndex.Add(i);
        }
    }
}
int CountForRange(int start, int end)
{
    return matchIndex.Count(x => x >= start && x <= end);
}

然后你可以这样使用,例如:

RunIndex(myList);
// I don't know what code you have here, this is just basic example.
for(int i = 0; i <= 10,000; i++)
{
    int count = CountForRange(startOfRange, endOfRange);
    // Do something with count.
}

另外,如果你检查的范围有很多重复,那么你可以考虑在字典中缓存范围计数,但在这个阶段很难判断这样做是否值得。

我很确定一个简单的迭代解决方案会表现得更好:

private int ExtractNumbers(List<string> myList, int start, int end)
{
    int count = 0;
    for (int i = start; i <= end; i++)
    {
        if (myList[i].Contains("MYNUMBER:"))
        {
            count++;
        }
    }
    return count;
}

对于我的试验台 1000万 (多10倍)行

  var data = Enumerable
   .Range(1, 10000000)
   .Select(item => "123456789 bla-bla-bla " + "MYNUMBER:" + item.ToString())
   .ToList();
  Stopwatch sw = new Stopwatch();
  sw.Start();
  int result = ExtraktNumbers(data, 0, 10000000);
  sw.Stop();

我得到了这些结果:

2.78秒-你的初始实现

初始循环(2.60秒):

private int ExtraktNumbers(List<string> myList, int start, int end) {
  int result = 0;
  for (int i = start; i < end; ++i)
    if (myList[i].Contains("MYNUMBER:"))
      result += 1;
  return result;
}

PLinq (1.72 seconds):

   private int ExtraktNumbers(List<string> myList, int start, int end) {
      return myList
        .AsParallel() // <- Do it in parallel
        .Skip(start - 1)
        .Take(end - start)
        .Where(x => x.Contains("MYNUMBER:"))
        .Count();
    }

显式并行实现(1.66秒):

   private int ExtraktNumbers(List<string> myList, int start, int end) {
     long result = 0;
     Parallel.For(start, end, (i) => {
       if (myList[i].Contains("MYNUMBER:"))
         Interlocked.Increment(ref result);
     });
     return (int) result;
  }

我只是无法复制你的177

如果你从一开始就知道你想要考虑的间隔,那么循环一次列表可能是个好主意,就像上面的Dmytro和musefan建议的那样,所以我不会再重复同样的想法了。

然而,我有一个不同的建议,以提高性能。你如何创建你的清单?你事先知道项目的数量吗?因为对于如此大的列表,您可以通过使用List<T>构造函数来获得初始容量,从而显著提高性能。