2008-11-21 21 views
7

Tôi thường sử dụng đệ quy 'khách' này trong F #Viết F # recursive khách truy cập thư mục trong C# - seq vs IEnumerable

let rec visitor dir filter= 
    seq { yield! Directory.GetFiles(dir, filter) 
      for subdir in Directory.GetDirectories(dir) do yield! visitor subdir filter} 

Gần đây tôi đã bắt đầu làm việc về việc thực hiện một số F # chức năng trong C#, và tôi cố gắng để tái sản xuất này như IEnumerable, nhưng tôi đang gặp khó khăn nhận được bất kỳ xa hơn thế:

static IEnumerable<string> Visitor(string root, string filter) 
{ 
    foreach (var file in Directory.GetFiles(root, filter)) 
     yield return file; 
    foreach (var subdir in Directory.GetDirectories(root)) 
     foreach (var file in Visitor(subdir, filter)) 
      yield return file; 
} 

những gì tôi không hiểu là tại sao tôi phải làm một foreach đôi trong phiên bản C# cho đệ quy, nhưng không phải trong F # ... Liệu seq {} ngầm có làm một 'concat' không?

Trả lời

12

yield! thực hiện thao tác 'làm phẳng', vì vậy nó tích hợp chuỗi bạn đã truyền vào chuỗi bên ngoài, ngầm thực hiện foreach trên mỗi phần tử của chuỗi và yield trên mỗi phần tử.

+1

Vì vậy, tôi có thể làm một .SelectMany để tái tạo điều này trong C#? – Benjol

+1

'Lợi nhuận của sản phẩm' của C# chỉ có thể trả về một mục tại một thời điểm, vì vậy bạn bị mắc kẹt với những lời rao giảng của mình, tôi sợ. – Sunlight

3

Không có cách nào đơn giản để thực hiện việc này. Bạn có thể workaround này bằng cách định nghĩa một kiểu C# có thể lưu trữ hoặc là một giá trị hoặc một chuỗi các giá trị - bằng cách sử dụng F # ký hiệu nó sẽ là:

type EnumerationResult<'a> = 
    | One of 'a 
    | Seq of seq<'a> 

(dịch này đến C# trong bất kỳ cách nào bạn thích :-))

Bây giờ, bạn có thể viết một cái gì đó như:

static IEnumerable<EnumerationResult<string>> Visitor 
     (string root, string filter) { 
    foreach (var file in Directory.GetFiles(root, filter)) 
     yield return EnumerationResult.One(file); 
     foreach (var subdir in Directory.GetDirectories(root)) 
      yield return EnumerationResult.Seq(Visitor(subdir, filter)) 
    } 
} 

để sử dụng nó, bạn sẽ phải viết một hàm flattens EnumerationResult, mà có thể là một phương pháp khuyến nông trong C# với các chữ ký sau:

IEnumerable<T> Flatten(this IEnumerable<EnumerationResult<T>> res); 

Bây giờ, đây là một phần mà nó trở nên phức tạp - nếu bạn thực hiện điều này một cách thẳng thắn, nó vẫn sẽ chứa "forach" để lặp qua kết quả "Seq" lồng nhau. Tuy nhiên, tôi tin rằng bạn có thể viết một phiên bản tối ưu hóa mà sẽ không có độ phức tạp bậc hai.

Ok .. Tôi đoán đây là một chủ đề cho một bài đăng trên blog chứ không phải là một cái gì đó có thể được mô tả đầy đủ ở đây :-), nhưng hy vọng, nó cho thấy một ý tưởng mà bạn có thể thử sau!

[EDIT: Nhưng tất nhiên, bạn cũng có thể sử dụng thực hiện ngây thơ của "Flatten" mà sẽ sử dụng "SelectMany" chỉ để làm cho ngôn ngữ C# iterator đang đẹp hơn của bạn]

2

Trong trường hợp cụ thể của lấy tất cả các file dưới một thư mục cụ thể, this overload of Directory.GetFiles hoạt động tốt nhất:

static IEnumerable<string> Visitor(string root, string filter) { 
    return Directory.GetFiles(root, filter, SearchOption.AllDirectories); 
} 


trong trường hợp tổng quát của đi qua một cây của các đối tượng đếm được, một vòng lặp foreach lồng nhau hoặc tương đương được yêu cầu (xem thêm: All About Iterators).


Edit: Added một ví dụ về một chức năng để san bằng bất kỳ cây vào một đếm:

static IEnumerable<T> Flatten<T>(T item, Func<T, IEnumerable<T>> next) { 
    yield return item; 
    foreach(T child in next(item)) 
    foreach(T flattenedChild in Flatten(child, next)) 
     yield return flattenedChild; 
} 

này có thể được sử dụng để chọn tất cả các file lồng nhau, như trước đây:

static IEnumerable<string> Visitor(string root, string filter) { 
    return Flatten(root, dir => Directory.GetDirectories(dir)) 
    .SelectMany(dir => Directory.GetFiles(dir, filter)); 
} 
+1

Thực ra, quá tải cụ thể đó có vấn đề thực tế nghiêm trọng; cụ thể là nếu * bất kỳ tệp * hoặc thư mục nào trong không gian tìm kiếm không hợp lệ do có đường dẫn quá dài hoặc người dùng không có quyền thích hợp hoặc bất kỳ ngoại lệ IO nào khác, toàn bộ thao tác sẽ bị hủy và không trả về kết quả nào. Ngược lại, bằng cách sử dụng tìm kiếm đệ quy theo cách thủ công, không có vấn đề như vậy; bạn có thể cố gắng nắm bắt từng danh sách của từng thư mục. –

2

Trong C#, tôi sử dụng mã sau cho loại chức năng này:

public static IEnumerable<DirectoryInfo> TryGetDirectories(this DirectoryInfo dir) { 
    return F.Swallow(() => dir.GetDirectories(),() => new DirectoryInfo[] { }); 
} 
public static IEnumerable<DirectoryInfo> DescendantDirs(this DirectoryInfo dir) { 
    return Enumerable.Repeat(dir, 1).Concat(
     from kid in dir.TryGetDirectories() 
     where (kid.Attributes & FileAttributes.ReparsePoint) == 0 
     from desc in kid.DescendantDirs() 
     select desc); 
} 

Điều này giải quyết các lỗi IO (không thể tránh khỏi), và tránh các vòng vô hạn do các liên kết tượng trưng (đặc biệt, bạn sẽ chạy vào tìm kiếm một số thư mục trong các cửa sổ 7).

+0

Cả hai đều hoàn chỉnh và súc tích – Hans

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