【问题标题】:LINQ method for adding items to a dictionary将项目添加到字典的 LINQ 方法
【发布时间】:2011-01-08 06:53:50
【问题描述】:

我正在尝试通过在 C# 中实现 Peter Norvig 的 spelling corrector 来更多地了解 LINQ。

第一部分涉及获取一个大的file of words(大约 100 万)并将其放入字典中,其中key 是单词,value 是出现次数。

我通常会这样做:

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

其中allWordsIEnumerable<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);                     
}

【问题讨论】:

  • 除非您发布基准代码,否则无法评论差异。
  • 我刚刚为你添加了这个问题。
  • 感谢分享 Peter Norvig 拼写校正器的链接。

标签: .net linq performance foreach


【解决方案1】:

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

  1. (内部)来自 group by 运算符; 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);

【讨论】:

  • 我可以修改 LINQ 查询以克服这些问题并仍然获得相同的结果吗?
  • 据我所知,不是在 3.5 或 4.0 中,没有。为此,当您仅聚合数据时,ToDictionary 和 GroupBy 运算符需要合作。对于不会发生的内存中 LINQ。
【解决方案2】:

当我构建您的第二个示例,然后在 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>();
});

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

【讨论】:

  • 有道理,在LINQ中是否有更“直接”的方式来做到这一点?
  • 不是真的,据我所知。也许通过不同的选择表达式?一旦 group by 参与表达,我就超出了我的经验范围。
【解决方案3】:

通过完全滥用 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 更改为使用类似的集合表达式也不会使其更快。

【讨论】:

    【解决方案4】:

    您可以使用 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);
    

    【讨论】:

    • OP 从未要求在该领域提供任何解决方案。这只会重复工作代码,而不会出现任何问题。
    • 我这里没有问任何问题,这是对上述问题的解决方案。
    • 那么它回答了问题的哪一部分?
    • 谁问了上面的问题想知道如何将项目添加到字典中,这是使用工作单元和存储库模式将项目添加到字典的解决方案
    • @danio 无需批评人们的回答我希望这是真的。 Stack Overflow 有完整的审查系统可以做到这一点,否则 SO 很快就会变成一堆垃圾。比问题标题看得更远是回答者的责任。这个“答案”本质上是将问题中的一段工作代码转换为方法语法。 OP 并没有要求这样做,所以答案只不过是无用的混乱,会影响未来的读者。作者应该删除它,但他没有,因为他会失去 8 点声望。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-06
    相关资源
    最近更新 更多