向字典添加项目的 LINQ 方法

问题描述 投票:0回答:4

我正在尝试通过在 C# 中实现 Peter Norvig 的拼写校正器来更多地了解 LINQ。

第一部分涉及获取一个大的单词文件(大约 100 万个)并将其放入字典中,其中

key
是单词,
value
是出现的次数。

我通常会这样做:

foreach (var word in allWords)                                                    
{           
    if (wordCount.ContainsKey(word))
        wordCount[word]++;
    else
        wordCount.Add(word, 1);
}

其中

allWords
IEnumerable<string>

在 LINQ 中我目前正在这样做:

var wordCountLINQ = (from word in allWordsLINQ
                         group word by word
                         into groups
                         select groups).ToDictionary(g => g.Key, g => g.Count());  

我通过查看所有

<key, value>
来比较这两个词典,它们是相同的,因此它们产生相同的结果。

foreach
循环需要3.82秒,LINQ查询需要4.49秒

我使用 Stopwatch 类对其进行计时,并且在 RELEASE 模式下运行。我不认为性能很差,我只是想知道是否有差异的原因。

我是否以低效的方式执行 LINQ 查询,或者我是否遗漏了某些内容?

这是完整的基准代码示例:

public static void TestCode()
{
    //File can be downloaded from http://norvig.com/big.txt and consists of about a million words.
    const string fileName = @"path_to_file";
    var allWords = from Match m in Regex.Matches(File.ReadAllText(fileName).ToLower(), "[a-z]+", RegexOptions.Compiled)
                   select m.Value;
    
    var wordCount = new Dictionary<string, int>();
    var timer = new Stopwatch();            
    timer.Start();
    foreach (var word in allWords)                                                    
    {           
        if (wordCount.ContainsKey(word))
            wordCount[word]++;
        else
            wordCount.Add(word, 1);
    }
    timer.Stop();
    
    Console.WriteLine("foreach loop took {0:0.00} ms ({1:0.00} secs)\n",
            timer.ElapsedMilliseconds, timer.ElapsedMilliseconds / 1000.0);
    
    //Make LINQ use a different Enumerable (with the exactly the same values), 
    //if you don't it suddenly becomes way faster, which I assmume is a caching thing??
    var allWordsLINQ = from Match m in Regex.Matches(File.ReadAllText(fileName).ToLower(), "[a-z]+", RegexOptions.Compiled)
                   select m.Value;
    
    timer.Reset();
    timer.Start();
    var wordCountLINQ = (from word in allWordsLINQ
                            group word by word
                            into groups
                            select groups).ToDictionary(g => g.Key, g => g.Count());  
    timer.Stop();
    
    Console.WriteLine("LINQ took {0:0.00} ms ({1:0.00} secs)\n",
            timer.ElapsedMilliseconds, timer.ElapsedMilliseconds / 1000.0);                     
}
.net linq performance foreach
4个回答
7
投票

LINQ 版本较慢的原因之一是创建了两个字典,而不是一个字典:

  1. (内部)来自运营商分组; group by 还存储每个单独的单词。您可以通过查看 ToArray() 而不是 Count() 来验证这一点。这是您实际不需要的大量开销。

  2. ToDictionary 方法基本上是实际 LINQ 查询上的 foreach,其中查询结果被添加到新字典中。根据唯一单词的数量,这也可能需要一些时间。

LINQ 查询速度稍慢的另一个原因是 LINQ 依赖于 lambda 表达式(Dathan 答案中的委托),并且与内联代码相比,调用委托会增加少量开销。

编辑:请注意,对于某些 LINQ 场景(例如 LINQ to SQL,但不是像这里这样的内存中 LINQ),重写查询会产生更优化的计划:

from word in allWordsLINQ 
group word by word into groups 
select new { Word = groups.Key, Count = groups.Count() }

但请注意,这不会为您提供字典,而是为您提供单词序列及其计数。您可以将其转换为字典

(from word in allWordsLINQ 
 group word by word into groups 
 select new { Word = groups.Key, Count = groups.Count() })
.ToDictionary(g => g.Word, g => g.Count);

1
投票

当我构建第二个示例,然后在 Reflector 的反汇编视图中打开它时,我得到以下信息:

Dictionary<string, int> wordCountLINQ = allWordsLINQ.GroupBy<string, string>(delegate (string word) {
    return word;
}).Select<IGrouping<string, string>, IGrouping<string, string>>(delegate (IGrouping<string, string> groups) {
    return groups;
}).ToDictionary<IGrouping<string, string>, string, int>(delegate (IGrouping<string, string> g) {
    return g.Key;
}, delegate (IGrouping<string, string> g) {
    return g.Count<string>();
});

可能需要更长的时间,因为发生了更多的函数调用,并且在一百万次迭代的过程中加起来。


0
投票

通过完全滥用 LINQ,我能够使其与 foreach 循环大致相同,并且通常比 foreach 循环稍快,即使使用委托调用也是如此:

var wordCountLINQ = allWordsLINQ.Aggregate(new Dictionary<string, int>(), (wcld, w) => { wcld[w] = (wcld.ContainsKey(w) ? wcld[w] : 0) + 1; return wcld; })

即使更改

foreach
以使用类似的集合表达式也没有使其更快。


0
投票

您可以使用 lambda 表达式解决您的问题:

var words = unitOfWork.DepartmentRepository.Get()
           .GroupBy(a=>a.word).Select(s    => new 
           {
             Word = s.Key,
             Count = s.Count()
           }).ToDictionary(d=>d.Word, d=>d.Count);
© www.soinside.com 2019 - 2024. All rights reserved.