2012-08-27 22 views
7

Tôi thấy mình thường xuyên xử lý đối tượng IEnumerable mà tôi cần lặp qua tính toán cho từng phần tử phụ thuộc vào n ngay trước đó và các đối tượng sau đây.C# Lặp qua một IEnumerable để tính toán sử dụng n phần tử trước đó và n

Một ví dụ phổ biến là tính trung bình cán, nhưng đôi khi việc tính toán phức tạp hơn đó và dựa vào một số lĩnh vực từ mỗi yếu tố của danh sách

Tôi không bao giờ chắc chắn về cách tốt nhất để cấu trúc vòng của tôi. Hiệu quả vấn đề, nhưng khả năng bảo trì và dễ đọc là quan trọng hơn.

  • Đôi khi tôi chuyển sang một danh sách và sau đó sử dụng một vòng lặp for để có được các yếu tố [i-1], [i], [i + 1] và sau đó thực hiện tính toán của tôi.

  • Lần khác tôi giữ nó như một IEnumerable, nhưng tôi "cache" vài phần tử trước đó, để tôi không tính toán cho đến khi tôi nhận được [i + 1] trong vòng lặp foreach .

  • Tôi cũng xem xét sử dụng danh sách được liên kết, để tôi có thể sử dụng các phương thức .Previous và .Next.

Bất kỳ đề xuất nào về kỹ thuật nào sẽ là cách tốt nhất để sử dụng?

+0

Không sẽ tỏ bày ngay bây giờ (quá nhiều ưu và nhược điểm cho một câu trả lời ngắn, và tôi không có thời gian cho một câu trả lời dài), nhưng một khả năng khác cho một số trường hợp là hai chỉ dẫn bạn di chuyển song song. 'i' bắt đầu bằng ví dụ: 0 và 'j' ở 3, bạn làm việc với' k' từ 'i' sang' j' (nửa mở, vì vậy nhấn 0, 1 & 2), sau đó bạn di chuyển '++ i; ++ j; 'và loop' k' một lần nữa, cho đến khi '++ j> list.Count' (vì vậy j chỉ nằm ngoài danh sách trên pass cuối cùng). –

Trả lời

2

Dưới đây là một việc thực hiện đơn giản:

public static IEnumerable<double> RollingAverage(this IEnumerable<double> values, int count) 
{ 
    var queue = new Queue<double>(); 
    foreach (var v in values) 
    { 
     queue.Enqueue(v); 
     if (queue.Count == count) 
     { 
      yield return queue.Average(); 
      queue.Dequeue(); 
     } 
    } 
} 

Nó có thể có thể được cải thiện, nhưng có vẻ như để làm việc ...

EDIT: đây là một phiên bản tốt hơn một chút (nó không cần phải liệt kê các để tính mức trung bình):

public static IEnumerable<double> RollingAverage(this IEnumerable<double> values, int count) 
{ 
    var queue = new Queue<double>(); 
    double sum = 0.0; 
    foreach (var v in values) 
    { 
     sum += v; 
     queue.Enqueue(v); 
     if (queue.Count == count) 
     { 
      yield return sum/count; 
      sum -= queue.Dequeue(); 
     } 
    } 
} 
6

Một tùy chọn sẽ là tạo phương pháp mở rộng cung cấp "cửa sổ" lăn mà bạn có thể sử dụng. Điều này sẽ cho phép bạn viết vòng lặp của bạn một cách đơn giản:

IEnumerable<IList<T>> CreateRollingWindow(IEnumerable<T> items, int size) 
{ 
    LinkedList<T> list = new LinkedList<T>(); 

    foreach(var item in items) 
    { 
     list.AddLast(item); 
     if (list.Count == size) 
     { 
      yield return list.ToList(); 
      list.RemoveFirst(); 
     } 
    } 
} 

này sau đó sẽ cho phép bạn viết các thuật toán của bạn chỉ đơn giản như:

foreach(var window as collection.CreateRollingWindow(5)) 
{ 
    double rollingAverage = window.Average(); // window is IList<T> here 
} 
+2

Hết sức tò mò - tại sao việc giảm giá? –

+0

Có lẽ ai đó không hiểu câu trả lời của bạn ... Dù sao, tôi thích thực tế là nó chung chung và có thể tái sử dụng cho các kịch bản khác nhau, nhưng tôi tự hỏi liệu có cách nào làm cho nó hiệu quả hơn không ... Nếu bạn có thể giả định rằng danh sách sẽ được tiêu thụ trước lần lặp tiếp theo, có thể bạn có thể trả lại trực tiếp, điều này sẽ tránh sao chép dữ liệu vào danh sách mới. –

+0

@ThomasLevesque Có. Tôi cố ý sử dụng 'LinkedList ' thay vì 'Queue ' cho cuộc gọi ToList() - nhưng nó cho phép nó tổng quát hơn và an toàn hơn, vì bạn có thể sử dụng nó trong các kịch bản thực thi trì hoãn, PLINQ, v.v. –

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