2011-02-08 25 views
9

Về cơ bản tôi tự hỏi nếu nó ok để sử dụng một liệt kê nhiều hơn một lần trong mã sau đó. Cho dù bạn phá vỡ sớm hay không, sẽ liệt kê luôn luôn đặt lại trong mọi trường hợp foreach dưới đây, do đó, cho chúng tôi liệt kê nhất quán từ đầu đến cuối của bộ sưu tập hiệu ứng?Bạn có thể sử dụng lại các bộ sưu tập IEnumerable nhiều lần không?

var effects = this.EffectsRecursive; 

foreach (Effect effect in effects) 
{ 
... 
} 

foreach (Effect effect in effects) 
{ 
    if(effect.Name = "Pixelate") 
     break; 
} 

foreach (Effect effect in effects) 
{ 
... 
} 

EDIT: Việc thực hiện EffectsRecursive là thế này:

public IEnumerable<Effect> Effects 
{ 
    get 
    { 
     for (int i = 0; i < this.IEffect.NumberOfChildren; ++i) 
     { 
      IEffect effect = this.IEffect.GetChildEffect (i); 
      if (effect != null) 
       yield return new Effect (effect); 
     } 
    } 
} 

public IEnumerable<Effect> EffectsRecursive 
{ 
    get 
    { 
     foreach (Effect effect in this.Effects) 
     { 
      yield return effect; 
      foreach (Effect seffect in effect.ChildrenRecursive) 
       yield return seffect; 
     } 
    } 
} 
+0

Btw tôi thêm việc thực hiện EffectsRecursive. –

+1

Hãy coi chừng sản lượng đệ quy. Có rất nhiều ma thuật biên dịch đang diễn ra ở đây tạo ra các máy trạng thái. Nếu cấu trúc dữ liệu lớn, điều này có thể kém hiệu quả/phức tạp hơn bạn mong đợi. – spender

+0

@spender: Cảm ơn, có cách nào khác để tôi có thể sử dụng không? –

Trả lời

14

Mã tiêu thụ chuỗi là tốt. Khi người tiêu dùng chỉ ra, mã tạo ra kiểu liệt kê có thể có vấn đề về hiệu năng nếu cây bị sâu.

Giả sử tại điểm sâu nhất cây của bạn là bốn sâu; suy nghĩ về những gì xảy ra trên các nút có bốn sâu. Để có được nút đó, bạn lặp lại gốc, gọi một trình lặp, gọi một trình lặp, gọi là trình lặp, chuyển nút trở lại mã mà chuyển nút trở lại mã mà chuyển nút trở lại ... Thay vì chỉ bàn giao nút cho người gọi, bạn đã thực hiện một nhóm lữ đoàn nhỏ với bốn người trong đó, và họ đang tramping dữ liệu xung quanh từ đối tượng đến đối tượng trước khi nó cuối cùng được vào vòng lặp mà muốn nó.

Nếu cây chỉ có bốn sâu, không có vấn đề gì lớn. Nhưng giả sử cây là mười nghìn phần tử, và có một nghìn nút tạo thành một danh sách liên kết ở đầu và chín nghìn nốt còn lại ở phía dưới. Bây giờ khi bạn lặp lại chín nghìn nút đó, mỗi nút sẽ phải trải qua một nghìn lần lặp, với tổng cộng chín triệu bản sao để tìm nạp chín nghìn nút. (Tất nhiên, bạn có thể đã nhận được lỗi tràn ngăn xếp và cũng đã làm hỏng quy trình.)

Cách để giải quyết vấn đề này nếu bạn có nó là tự quản lý ngăn xếp thay vì đẩy trình lặp mới trên ngăn xếp .

public IEnumerable<Effect> EffectsNotRecursive() 
{  
    var stack = new Stack<Effect>(); 
    stack.Push(this); 
    while(stack.Count != 0) 
    { 
     var current = stack.Pop(); 
     yield return current; 
     foreach(var child in current.Effects) 
      stack.Push(child); 
    } 
} 

Việc triển khai ban đầu có độ phức tạp về thời gian trong đó n là số nút và d là độ sâu trung bình của cây; vì d có thể trong trường hợp xấu nhất là O (n), và trong trường hợp tốt nhất là O (lg n), điều đó có nghĩa là thuật toán nằm giữa O (n lg n) và O (n^2) trong thời gian. Nó là O (d) trong không gian heap (cho tất cả các vòng lặp) và O (d) trong không gian ngăn xếp (cho tất cả các cuộc gọi đệ quy.)

Việc thực hiện mới có độ phức tạp thời gian của O (n), và là O (d) trong không gian heap, và O (1) trong không gian ngăn xếp.

Một khía cạnh nhỏ hơn là thứ tự khác nhau; cây được di chuyển từ trên xuống dưới và phải sang trái trong thuật toán mới, thay vì từ trên xuống dưới và trái sang phải. Nếu điều đó làm phiền bạn thì bạn chỉ có thể nói

 foreach(var child in current.Effects.Reverse()) 

thay thế.

Đối với phân tích hơn về vấn đề này, xem bài viết đồng nghiệp của tôi Wes Dyer về đề tài này:

http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx

+0

@Eric: Cảm ơn Eric, đây là câu trả lời tuyệt vời. Tôi sẽ thay đổi các liệt kê đệ quy của tôi như thế này. Nhưng cuối cùng trong ví dụ của bạn, ví dụ của bạn, nó sẽ bắt đầu với phụ huynh trên cùng của 9000 mục, và sau đó lặp qua 9000 mục đó từ trên xuống dưới, và sau đó di chuyển qua các bậc cha mẹ hàng đầu khác bắt đầu sau lần đầu tiên cha mẹ chúng tôi lặp lại như là yếu tố đầu tiên, cho đến khi chúng tôi kết thúc với phần tử trên cùng bên trái nhất? Điều này có nghĩa là "cây được di chuyển từ trên xuống dưới và từ phải sang trái"? –

+0

@Eric: Btw dòng nơi bạn nói "stack.Push (this);" cũng trả về cá thể hiện tại, đúng không? Trong quá trình thực hiện, tôi chỉ muốn trả lại các hiệu ứng phụ của hiệu ứng hiện tại mà không có chính nó. Tôi có thể xóa nó không? Tôi cảm thấy như thế này sẽ gây ra các vấn đề trong stack.Pop() dòng. Bạn nghĩ sao? –

+1

@Janan: Re câu hỏi đầu tiên của bạn: giả sử cây là A với con trái B và con phải C. Thuật toán này mang lại cho chúng theo thứ tự A, C, B. Đầu tiên chúng ta đặt A trên ngăn xếp. Sau đó, chúng tôi bật A và đặt B và C lên ngăn xếp. Sau đó, chúng tôi bật C, sau đó chúng tôi bật B. B và C được thực hiện quyền sang trái thay vì trái sang phải. Nếu bạn muốn A, B, C, sau đó đảo ngược danh sách con trước khi chúng được đẩy. Đẩy A, pop A, đẩy C, đẩy B, pop B, pop C, và bây giờ chúng tôi có A, B, C. –

0

Tất cả phụ thuộc vào việc thực hiện của các loại EffectsRecursive.

6

Pháp lý, vâng. Cho dù nó sẽ hoạt động như bạn mong đợi phụ thuộc vào:

  • Việc thực hiện IEnumerable trả về bởi EffectsRecursive và liệu nó luôn luôn trả về cùng một tập;

  • Cho dù bạn muốn liệt kê cùng một tập hợp cả hai lần

Nếu nó trả về một IEnumerable mà đòi hỏi một số công việc chuyên sâu, và nó không cache kết quả trong nội bộ, sau đó bạn có thể cần phải .ToList() nó bản thân bạn. Nếu nó lưu trữ kết quả, thì ToList() sẽ hơi dư thừa nhưng có thể không gây hại.

Ngoài ra, nếu GetEnumerator() được thực hiện trong một điển hình/thích hợp (*) cách , sau đó bạn có thể an toàn liệt kê bất kỳ số lần - mỗi foreach sẽ là một cuộc gọi mới để GetEnumerator() mà trả về một mới dụ của IEnumerator. Nhưng có thể là trong một số trường hợp, nó trả về cùng một ví dụ IEnumerator đã được liệt kê một phần hoặc đầy đủ, vì vậy tất cả chỉ phụ thuộc vào mục đích sử dụng cụ thể của IEnumerable cụ thể đó.

* Tôi khá chắc chắn trả về cùng một điều tra nhiều lần thực sự là vi phạm hợp đồng ngụ ý cho mẫu, nhưng tôi đã thấy một số triển khai thực hiện việc đó.

12

Có điều này là hợp pháp để làm. Mẫu IEnumerable<T> có nghĩa là hỗ trợ nhiều bảng liệt kê trên một nguồn duy nhất. Bộ sưu tập chỉ có thể được liệt kê một lần nên hiển thị IEnumerator thay thế.

1

Rất có thể là có. Hầu hết các triển khai của IEnumerable trả về một IEnumerator mới bắt đầu từ đầu danh sách.

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