LINQ метод для добавления элементов в словарь - PullRequest
9 голосов
/ 22 января 2010

Я пытаюсь узнать немного больше о LINQ, внедрив корректор орфографии Питера Норвига в C #.

Первая часть включает в себя большой файл слов (около 1 миллиона) и размещение его в словаре, где 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());  

Я сравниваю 2 словаря, глядя на все <key, value>, и они идентичны, поэтому они дают одинаковые результаты.

Цикл foreach занимает 3,82 с , а запрос LINQ занимает 4,49 с

Я синхронизирую его, используя класс Секундомер, и я работаю в режиме РЕЛИЗ. Я не думаю, что производительность плохая, мне просто интересно, была ли причина для разницы.

Я выполняю запрос 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);                     
}

Ответы [ 4 ]

6 голосов
/ 22 января 2010

Одна из причин, по которой версия LINQ медленнее, заключается в том, что вместо одного словаря создаются два словаря:

  1. (внутренне) из группы по оператору; группа также хранит каждое отдельное слово. Вы можете убедиться в этом, посмотрев на ToArray (), а не на Count (). Это много накладных расходов, которые вам на самом деле не нужны.

  2. Метод ToDictionary в основном является foreach по сравнению с фактическим запросом LINQ, где результаты запроса добавляются в новый словарь. В зависимости от количества уникальных слов это также может занять некоторое время.

Другая причина того, что запрос LINQ немного медленнее, заключается в том, что LINQ полагается на лямбда-выражения (делегат в ответе Дафана), а вызов делегата добавляет незначительные издержки по сравнению со встроенным кодом.

Редактировать: Обратите внимание, что для некоторых сценариев 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 голос
/ 22 января 2010

Когда я создаю ваш второй пример и затем открываю его в представлении разборки 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 голосов
/ 11 мая 2017

Полностью злоупотребив LINQ, я смог заставить его работать примерно так же и часто немного быстрее, чем цикл foreach, даже с вызовом делегата:

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

Даже изменение foreach для использования подобного выражения set не сделало его быстрее.

0 голосов
/ 10 мая 2017

Вы можете решить свою проблему, используя лямбда-выражение:

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...