Il codice che consuma la sequenza è soddisfacente. Come sottolinea lo spender, il codice che produce l'enumerazione potrebbe avere problemi di prestazioni se l'albero è profondo.
Supponiamo che nel punto più profondo il vostro albero fa quattro profonda; pensa a cosa succede sui nodi che sono quattro in profondità. Per ottenere quel nodo, si esegue l'iterazione della radice, che chiama un iteratore, che chiama un iteratore, che chiama un iteratore, che passa di nuovo il nodo al codice che restituisce il nodo al codice che passa il nodo indietro ... Invece di solo consegnando il nodo al chiamante, hai creato un piccolo gruppo di brigata con quattro ragazzi al suo interno e stanno calpestando i dati da un oggetto all'altro prima che raggiungano il loop che lo desiderava.
Se l'albero ha solo quattro profondità, probabilmente non è un grosso problema. Ma supponiamo che l'albero sia diecimila elementi e che abbia un migliaio di nodi che formano una lista collegata nella parte superiore e i rimanenti novemila nodi sul fondo. Ora, quando si iterano quei novemila nodi, ognuno deve passare attraverso mille iteratori, per un totale di nove milioni di copie per ottenere novemila nodi. (Ovviamente, probabilmente hai ottenuto un errore di overflow dello stack e hai bloccato anche il processo.)
Il modo di affrontare questo problema, se ce l'hai, è gestire lo stack tu stesso piuttosto che spingere nuovi iteratori nello stack .
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);
}
}
L'implementazione originale ha una complessità temporale O (nd) dove n è il numero di nodi e d è la profondità media dell'albero; poiché d può nel caso peggiore essere O (n), e nel migliore dei casi essere O (lg n), ciò significa che l'algoritmo è compreso tra O (n lg n) e O (n^2) nel tempo. È O (d) nello spazio heap (per tutti gli iteratori) e O (d) nello spazio stack (per tutte le chiamate ricorsive.)
La nuova implementazione ha una complessità temporale di O (n), ed è O (d) nello spazio heap e O (1) nello spazio stack.
Un lato negativo di questo è che l'ordine è diverso; l'albero è attraversato dall'alto verso il basso e da destra a sinistra nel nuovo algoritmo, anziché dall'alto verso il basso e da sinistra a destra. Se questo ti disturba, puoi semplicemente dire
foreach(var child in current.Effects.Reverse())
invece.
Per ulteriori analisi di questo problema, vedere l'articolo del mio collega Wes Dyer sul tema:
http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx
Btw ho aggiunto l'attuazione di EffectsRecursive. –
Attenzione al rendimento ricorsivo. C'è un sacco di magia del compilatore in corso qui per creare macchine di stato. Se la struttura dei dati è ampia, potrebbe essere meno performante/più complessa di quanto ci si aspetti. – spender
@spender: Grazie, c'è un modo alternativo che posso usare? –