2010-01-22 44 views
8

Tôi đang cố gắng tìm hiểu thêm một chút về LINQ bằng cách triển khai số spelling corrector của Peter Norvig trong C#.Phương pháp LINQ để thêm các mục vào từ điển

Phần đầu tiên liên quan đến việc lấy một số lớn file of words (khoảng 1 triệu) và đặt nó vào từ điển trong đó key là từ và value là số lần xuất hiện.

Tôi thường muốn làm điều này như sau:

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

đâu allWords là một IEnumerable<string>

Trong LINQ Tôi hiện đang làm việc đó như thế này:

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

tôi so sánh 2 từ điển bằng cách xem tất cả các số <key, value> và chúng giống hệt nhau, vì vậy chúng tạo ra cùng một kết quả.

Vòng lặp foreach mất 3,82 giây và truy vấn LINQ mất 4,49 giây

tôi thời gian đó bằng cách sử dụng lớp Stopwatch và tôi đang chạy trong chế độ phát hành. Tôi không nghĩ rằng hiệu suất là xấu Tôi đã chỉ tự hỏi nếu có một lý do cho sự khác biệt.

Tôi có đang thực hiện truy vấn LINQ một cách không hiệu quả hoặc tôi thiếu gì đó không?

Cập nhật: đây là mẫu mã chuẩn đầy đủ:

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);      
} 
+1

Không thể nhận xét về sự khác biệt trừ khi bạn đăng mã chuẩn. – JaredPar

+0

Tôi vừa thêm vào câu hỏi cho bạn. –

Trả lời

5

Một trong những lý do phiên bản LINQ là chậm hơn, là bởi vì thay vì một từ điển, hai bộ từ điển được tạo ra:

  1. (nội bộ) khỏi nhóm bởi nhà điều hành; nhóm cũng lưu trữ từng từ riêng lẻ. Bạn có thể xác minh điều này bằng cách nhìn vào một ToArray() chứ không phải là một Count(). Đây là rất nhiều chi phí bạn không thực sự cần trong trường hợp của bạn.

  2. Phương pháp ToDictionary về cơ bản là tìm kiếm qua truy vấn LINQ thực tế, nơi kết quả từ truy vấn được thêm vào từ điển mới. Tùy thuộc vào số lượng từ duy nhất, điều này cũng có thể mất một thời gian.

Một lý do khác mà các truy vấn LINQ là chậm hơn một chút, là bởi vì LINQ dựa vào biểu thức lambda (các đại biểu trong câu trả lời than), và gọi một đại biểu cho biết thêm một số lượng nhỏ của chi phí so với mã nội tuyến.

Edit: Lưu ý rằng đối với một số kịch bản LINQ (như LINQ to SQL, nhưng không phải trong bộ nhớ LINQ như ở đây), viết lại truy vấn tạo ra một kế hoạch tối ưu hóa hơn:

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

Lưu ý tuy nhiên , điều này không cung cấp cho bạn một từ điển, mà là một chuỗi các từ và số lượng của chúng. Bạn có thể chuyển đổi này vào một từ điển với

(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); 
+0

Tôi có thể sửa đổi truy vấn LINQ để khắc phục những vấn đề này và vẫn nhận được kết quả tương tự không? –

+0

Theo như tôi biết, không phải trong 3.5 hoặc 4.0, không. Để làm việc này, các toán tử ToDictionary và GroupBy sẽ cần phải hợp tác khi bạn chỉ tổng hợp dữ liệu. Đối với LINQ trong bộ nhớ không xảy ra. – Ruben

1

Khi tôi xây dựng ví dụ thứ hai của mình và sau đó mở ở chế độ xem tháo Reflector, tôi nhận được như sau:

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>(); 
}); 

lẽ nó mất nhiều thời gian hơn chỉ vì có nhiều cuộc gọi chức năng hơn xảy ra, và trong quá trình một triệu lần lặp lại tăng lên.

+0

Điều đó có ý nghĩa, có cách nào "trực tiếp" hơn để làm điều đó trong LINQ không? –

+0

Không thực sự, mà tôi biết. Có thể bởi một biểu thức chọn khác? Tôi ra khỏi lĩnh vực kinh nghiệm của tôi ngay sau khi nhóm được tham gia vào các biểu hiện. – Dathan

0

Bạn có thể giải quyết vấn đề bằng cách sử dụng biểu thức 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); 
+0

OP không bao giờ yêu cầu bất kỳ giải pháp nào trong khu vực đó. Điều này chỉ lặp lại mã làm việc mà không có bất kỳ vấn đề nào từ câu hỏi. –

+0

Tôi không hỏi bất kỳ câu hỏi nào ở đây, đó là giải pháp cho câu hỏi trên. –

+0

Vậy một phần câu hỏi là gì? –

0

Bằng cách hoàn toàn lạm dụng LINQ tôi đã có thể làm cho nó được xung quanh giống nhau và thường nhanh hơn một chút hơn vòng lặp foreach, ngay cả với một cuộc gọi đại biểu:

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

Ngay cả việc thay đổi foreach sử dụng một biểu thức thiết lập tương tự đã không làm cho nó nhanh hơn .

Các vấn đề liên quan