2009-07-13 55 views
9

Tôi có một phương thức thực hiện một 'grep' đơn giản trên các tệp, sử dụng một số "chuỗi tìm kiếm". (Có hiệu quả, tôi đang làm rất ngây thơ "Tìm Mọi Tham khảo")Làm thế nào để làm cho một C# 'grep' hơn Chức năng sử dụng LINQ?

IEnumerable<string> searchStrings = GetSearchStrings(); 
IEnumerable<string> filesToLookIn = GetFiles(); 
MultiMap<string, string> references = new MultiMap<string, string>(); 

foreach(string fileName in filesToLookIn) 
{ 
    foreach(string line in File.ReadAllLines(fileName)) 
    { 
     foreach(string searchString in searchStrings) 
     { 
      if(line.Contains(searchString)) 
      { 
       references.AddIfNew(searchString, fileName); 
      } 
     } 
    } 
} 

Lưu ý: MultiMap<TKey,TValue> là gần giống như Dictionary<TKey,List<TValue>>, chỉ cần tránh NullReferenceExceptions bạn thường gặp phải.


Tôi đã cố gắng đưa điều này vào một phong cách "chức năng" hơn, bằng cách sử dụng các phương pháp mở rộng LINQ bị xích nhưng chưa tìm ra.

Một nỗ lực cụt:

// I get lost on how to do a loop within a loop here... 
// plus, I lose track of the file name 
var lines = filesToLookIn.Select(f => File.ReadAllLines(f)).Where(// ??? 

Và khác (hy vọng duy trì tên tập tin thời gian này):

var filesWithLines = 
    filesToLookIn 
     .Select(f => new { FileName = f, Lines = File.ReadAllLines(f) }); 

var matchingSearchStrings = 
    searchStrings 
     .Where(ss => filesWithLines.Any(
         fwl => fwl.Lines.Any(l => l.Contains(ss)))); 

Nhưng tôi vẫn dường như mất thông tin mà tôi cần.

Có lẽ tôi đang tiếp cận điều này từ góc độ sai? Từ quan điểm hiệu suất, các vòng lặp phải thực hiện theo thứ tự tương tự như ví dụ ban đầu.

Bất kỳ ý tưởng nào về cách thực hiện điều này trong một đại diện chức năng nhỏ gọn hơn?

Trả lời

9

Làm thế nào về:

var matches = 
    from fileName in filesToLookIn 
    from line in File.ReadAllLines(fileName) 
    from searchString in searchStrings 
    where line.Contains(searchString) 
    select new 
    { 
     FileName = fileName, 
     SearchString = searchString 
    }; 

    foreach(var match in matches) 
    { 
     references.AddIfNew(match.SearchString, match.FileName); 
    } 

Edit:

Về mặt lý thuyết, truy vấn biến mỗi tên tập tin vào một tập hợp các đường nét, sau đó xuyên tham gia mà bộ dây chuyền với tập hợp các chuỗi tìm kiếm (nghĩa là mỗi dòng được ghép nối với mỗi chuỗi tìm kiếm). Tập hợp đó được lọc theo các dòng phù hợp và thông tin liên quan cho mỗi dòng được chọn.

Nhiều mệnh đề from tương tự như câu lệnh lồng nhau foreach. Mỗi chỉ báo một lần lặp mới trong phạm vi của phiên bản trước đó. Nhiều mệnh đề from dịch sang phương thức SelectMany, chọn một chuỗi từ mỗi phần tử và làm phẳng chuỗi kết quả thành một chuỗi.

Tất cả cú pháp truy vấn của C# dịch sang các phương pháp mở rộng.Tuy nhiên, trình biên dịch không sử dụng một số thủ thuật. Một là sử dụng các loại ẩn danh. Bất cứ khi nào có 2 biến phạm vi nằm trong cùng phạm vi, chúng có thể là một phần của loại ẩn danh đằng sau hậu trường. Điều này cho phép số lượng dữ liệu có phạm vi tùy ý chảy qua các phương pháp mở rộng như SelectWhere, có số lượng đối số cố định. Xem this post để biết thêm chi tiết.

Dưới đây là bản dịch phương pháp mở rộng của các truy vấn trên:

var matches = filesToLookIn 
    .SelectMany(
     fileName => File.ReadAllLines(fileName), 
     (fileName, line) => new { fileName, line }) 
    .SelectMany(
     anon1 => searchStrings, 
     (anon1, searchString) => new { anon1, searchString }) 
    .Where(anon2 => anon2.anon1.line.Contains(anon2.searchString)) 
    .Select(anon2 => new 
    { 
     FileName = anon2.anon1.fileName, 
     SearchString = anon2.searchString 
    }); 
+1

Tôi không biết bạn có thể sử dụng nhiều "từ" tuyên bố như thế. Làm thế nào mà thực sự làm việc? Trải nghiệm của tôi với LINQ hoàn toàn thông qua lambdas và các phương pháp mở rộng. Điều này thậm chí dịch để xích các phương pháp mở rộng? –

+0

Có, nhiều từ các mệnh đề chuyển thành các cuộc gọi đến phương thức mở rộng SelectMany. Kiểm tra nó trong Reflector để xem chính xác những gì đang xảy ra. – dahlbyk

+0

@jmitchem: Tôi đã chỉnh sửa câu trả lời của mình để giải quyết các câu hỏi của bạn. –

3

Tôi sẽ sử dụng các cuộc gọi API FindFile (FindFirstFileEx, FindNextFile, v.v.) để tìm trong tệp cho cụm từ bạn đang tìm kiếm. Nó có thể sẽ làm điều đó nhanh hơn bạn đọc từng dòng một. Tuy nhiên, nếu điều đó không hiệu quả với bạn, bạn nên xem xét việc thực hiện IEnumerable<String> để đọc các dòng từ tệp và mang chúng khi chúng được đọc (thay vì đọc tất cả chúng thành một mảng). Sau đó, bạn có thể truy vấn trên mỗi chuỗi và chỉ nhận được chuỗi tiếp theo nếu cần.

Điều này sẽ giúp bạn tiết kiệm rất nhiều thời gian. Lưu ý rằng trong .NET 4.0, rất nhiều IO apis trả về các dòng từ các tệp (hoặc các tệp tìm kiếm) sẽ trả về các triển khai IEnumerable thực hiện chính xác những gì được đề cập ở trên, trong đó nó sẽ tìm kiếm các thư mục/tệp và tạo chúng khi thích hợp thay vì tải trước tất cả các kết quả.

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