In questi gg di "lenta" preparazione in vista dell' esame 70-536 ho deciso di approfondire alcuni caratteristiche sia del framework che di C# 2.0  ...

Mi sono detto: perchè nn buttare giù alcune linee di codice che coprano Generics , Custom Iterator conditi con alcune reminescenze universitarie che vanno sotto il nome di alberi binari?!!!

Cominciano con la definizione della struttura dati presa in esame, utilizzando una definizione ricorsiva:

Un albero binario è composto da un nodo radice, da un sottolabero
destro e da un sottoalbero sinistro

 

Dalla definizione sopra esposta viene in modo naturale definire una classe del tipo: 

   1: public class BinaryTree where T:IComparable
   2: {
   3:     private T dataValue;
   4:     private BinaryTree leftSubTree;       
   5:     private BinaryTree rightSubTree;
   6: }
   7:  

Come possiamo vedere la definizione ricorsiva di albero binario ci guida anche nella definizione della classe apposita; da notare il vincolo posto sul tipo generico :  con questo vincolo il tipo implementando l' interfaccia IComparable permette di utilizzare il metodo CompareTo() al fine di confrontare l'istanza sul quale viene invocato con un altra dello stesso tipo ( caratteristica desiderabile in fase di inserimento di nuovi nodi nell' albero); definito uno scheletro minimale per il nostro albero binario non ci resta che scrivere un bel custom iterator che permetta di visitarne tutti i nodi; gli iteratori offrono infatti un elevato grado di flessibilità al programmatore in quanto permettono di iterare sugli elementi seguendo la propria logica applicativa; supponiamo di voler visitare gli elementi del nostro albero seguendo la logica della visita InOrder (sottoalbero sinistro, radice, sottoalbero destro); una possibile implementazione basata su un iteratore custom è la seguente:

   1: public IEnumerable<string> InOrder(BinaryTree<T> root)
   2:         {
   3:             Stack<BinaryTree<T>> stack = new Stack<BinaryTree<T>>();
   4:             for (BinaryTree<T> current = root;
   5:                 current != null || stack.Count > 0;
   6:                 current = current.RightSubTree)
   7:             {
   8:                 while (current != null)
   9:                 {
  10:                     stack.Push(current);
  11:                     current = current.LeftSubTree;
  12:                 }
  13:                 current = stack.Pop();
  14:                 yield return current.DataValue.ToString();
  15:             }
  16:         }

Come possiamo vedere l'implementazione di un iteratore in C# 2.0 segue deve rispettare due step fondamentali:

  1. Scrittura di una funzione il cui tipo di ritorno sia  IEnumerable oppure IEnumerable.
  2. Utilizzo all' interno della funzione definita al punto 1 delle keywords yield return oppure yield break rispettivamente per restituire un valore al chiamante oppure per abbandonare l'iterazione.

Nel metodo sopra definito ho utilizzato anche la classe generica Stack<> in modo tale che mentre si attraversa l 'albero ogni nodo figlio appartenente al sottoalbero sinistro venga inserito in cima allo stack;  l'istruzione yield return restituisce quindi prima gli elementi del sottoalbero sinistro poi la radice ed infine gli elementi del sottoalbero destro; nell' esempio sopra esposto ho adottato un approccio iterativo preferendolo ad uno ricorsivo per ragioni di performance;

E' possibile ad esempio grazie alla flessibilità degli iteratori custom cambiare la logica di visita degli elementi applicando magari una logica in cui prima viene visitata la radice, successivamente il sottoalbero destro ed infine quello sinistro; di seguito una possibile implementazione fornita da Nicolò Carandini:

   1: public IEnumerable<string> PreOrder(BinaryTree root)
   2:  
   3:          {
   4:              Stack> stack = new Stack>();
   5:              for (BinaryTree current = root;
   6:              current != null || stack.Count > 0;
   7:              current = current.LeftSubTree)
   8:              {
   9:                  if (current == null)
  10:                  {
  11:                      current = stack.Pop();
  12:                  }
  13:                  yield return current.DataValue.ToString();
  14:                  if (current.RightSubTree != null)
  15:                  {
  16:                      stack.Push(current.RightSubTree);
  17:                  }
  18:              }
  19:            } 

A questo punto, dato che gli iteratori restituiscono un tipo IEnumerable, è possibile lato chiamante iterare sugli elementi dell' albero ricorrendo banalmente ad un foreach:

Per una implementazione più completa dell' albero binario fatemi sapere che magari fornirò ulteriori dettagli in futuri post .

Ad maiora....

Technorati tags: ,