【问题标题】:Speed up working with big arrays of string in c#加快在 c# 中处理大字符串数组的速度
【发布时间】:2015-05-01 05:38:00
【问题描述】:

此方法从字符串数组中获取最常用的单词。 对于大型数组(例如 70.000 个字符串需要 190.000 毫秒),它的运行速度非常慢。 我已经测量(使用 Stopwatch())它的第一部分是最慢的:

public static List<WordDouble> MostFrequentWords(double count, string[] words)
    {                     
        var wordsAndNumbers = new List<WordDouble>();

        foreach (var word in words)
        {
            if (wordsAndNumbers.Exists(e => e.Word == word.ToLower()))
                wordsAndNumbers[wordsAndNumbers.FindIndex(e => e.Word == word.ToLower())].Count++;
            else
            {
                var addWord = new WordDouble();
                addWord.Word = word.ToLower();
                addWord.Count = 1;
                wordsAndNumbers.Add(addWord);
            }
         }     

/*method goes on, other parts work fast and do not need improvement */
...
return something;
}

public class WordDouble
    {
        public string Word;
        public double Count;
    }

如何提高此方法的性能?

【问题讨论】:

  • Code Review 将是您提问的更好地方。
  • 你听说过Dictionary吗?
  • 并不是说它会有很大的不同,而是为 word.ToLower() 创建一个变量,并在 3 个使用它的地方使用该变量
  • 使用ToLower可以通过一个word.Equals(e.Word, StringComparison.CurrentCultureIgnoreCase);正如已经提到的使用字典,您也可以为字典指定 StingComparison
  • stackoverflow.com/questions/2655759/… 显示了具有合理性能的代码 - 采用字符串应该很简单,因为它是 GroupBy/OrderBy 的基本组合

标签: c# list


【解决方案1】:

使用Exists 在列表中检查项目是 O(n) 操作,而在字典中检查项目是 O(1) 操作。

这会在很短的时间内运行(实际上大约是 1/2200 的时间):

Dictionary<string, int> wordsAndNumbers = new Dictionary<string, int>();

foreach (string word in words) {
  if (wordsAndNumbers.ContainsKey(word.ToLower())) {
    wordsAndNumbers[word.ToLower()]++;
  } else {
    wordsAndNumbers.Add(word.ToLower(), 1);
  }
 }

下面是 70000 个字符串的测试运行结果,分别是原始代码、我的代码和控制台的代码:

00:01:21.0804944
00:00:00.0360415
00:00:00.1060375

您甚至可以通过在循环中只执行一次ToLower 来加快速度:

var wordsAndNumbers = new Dictionary<string, int>();

foreach (var word in words) {
  string s = word.ToLower();
  if (wordsAndNumbers.ContainsKey(s)) {
    wordsAndNumbers[s]++;
  } else {
    wordsAndNumbers.Add(s, 1);
  }
}

试运行:

00:00:00.0235761

【讨论】:

  • 我认为您应该使用字典构造函数重载女巫采取比较器而不是频繁的 ToLower 强制转换,检查我的答案。
  • @Console:这就是你投反对票的理由吗?
  • 那么,谁投了反对票;为什么?如果你不解释你认为错的地方是什么,它就无法改进答案。
  • @Guffa 你实际上是对的,所以投票给你,但我想知道使用 PLINQ 会更快。您如何看待使用 PLINQ 来解决这个问题?
  • @Guffa 我确实改进了我的答案,它现在应该比 ToLower 实现稍微好一点。
【解决方案2】:

首先,为什么要使用双精度数来计算单词? 使用长字典,不要为了比较而将其转换为低。

Dictionary<string,long> wordsAndNumbers = new 
    Dictionary<string,long>(StringComparer.OrdinalIgnoreCase);

foreach(var word in words)
{
    if (!wordsAndNumbers.ContainsKey(word))
        wordsAndNumbers[word] = 1;
    else
        wordsAndNumbers[word]++;
 }

使用 70000 字,我得到以下运行时:00:00:00.0152345,这比我的机器上需要 00:00:00.0320127 的较低解决方案要快得多

【讨论】:

  • 使用带比较器的字典重载看起来不错,但实际上要慢得多。在我的回答中查看结果。
  • 我用 OridinalIgnoreCase 比较器替换了 CurrentCultureIgnoreCase,在我的机器上我的实现现在比 tolower 解决方案快 30%
  • 我认为你的意思是OrdinalIgnoreCase。它稍微快一些,但我只看到了 2% 的差异。
  • @Console 非常感谢您的详细解释。顺便说一句,它启发了我了解 IComparer 类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-14
  • 2011-07-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多