COMP 310
Spring 2010

Lec21: Generics and Extended Visitors Together

Home  Info  Owlspace  Resources

Today, we are going to try to wrap up some loose ends and try tie extended visitors and generics together.

Deletion algorithm for self-balancing trees.

Below is the code for the deletion algorithm.   Things to note:

/**
 * DeleteNAlgo  -  deletes the supplied parameter from a TreeN
 *  preserving balance with a maximum number of elements per node.
 */
public class DeleteNAlgo implements ITreeNAlgo {

	/**
	 * Splits child upwards if its state > order and splices it into
	 * its parent.  The supplied param is an ISpliceCmd used to do the
	 * splicing.
	 */
	private SplitUpAndApply splitUpAndSplice;

	/**
	 * Constructor for the class
	 * @param order -- the max possible number of elements in a node
	 */
	public DeleteNAlgo(int order) {
		splitUpAndSplice = new SplitUpAndApply(order);
	}

	public Object caseAt(int s, final TreeN host, Object... keys) {
		final Object key = keys[0];

		switch(s) {
		case 0: {
			return key;  // no-op for empty case
		}

		case 1: {
			collapse2Node(host);
		}// fall through to default case

		default: {
			return host.execute(new ITreeNAlgo() {
				public Object caseAt(int s_help, final TreeN h, Object... cmds) {
					switch(s_help) {
					case 0: {
						return key;
					}
					case 1: {
						if (h.getDat(0).equals(key)) {
							//System.out.println("Found data element!");
							Object d = h.getDat(0); // get key
							h.splitDownAt(0); //transition to empty
							return d;
						}
						else {
							//System.err.println("Element "+ key +" not in tree!");
							((ILambda)cmds[0]).apply(h);
							return h.getDat(0);
						}
					}
					default : {
						final int x = findX(h, s_help, ((Integer)key).intValue());
						TreeN newChild = collapse2Node(h.splitDownAt(x).getChild(x)); // push data down
						Object result = newChild.execute(this,
								new ILambda() {
							/**
							 * @param child a TreeN subtree of h.
							 */
							public Object apply(Object... children) {
								return h.spliceAt(x, (TreeN)children[0]);
							}
						});
						h.execute(splitUpAndSplice, cmds[0]);
						return result;
					}
					}
				}
			}
			, new ILambda() {  // Top-level no-op lambda
				public Object apply(Object... nu) {
					return host;
				}
			});
		}
		}
	}

	//------ Utility methods ------------------------------------------------------

	/**
	 * Utility method to collapses a 2-node tree by splicing it with
	 * its two children.
	 * @param t -- must be a 2-node tree!
	 */
	private final TreeN collapse2Node(TreeN t) {
		t.spliceAt(1,t.getChild(1));
		t.spliceAt(0,t.getChild(0));
		return t;
	}

	/**
	 * Utility method that finds the index of the data element that
	 * either matches the supplied key or is the first element bigger
	 * than the key.
	 * @param t - an TreeN
	 * @param state - the state of the tree
	 * @param k - the key to locate
	 * @return - the index of the data element such that if k exists in the
	 * tree, it is guaranteed to be in the 2-node tree
	 * defined by the data at the index and its left and right children.
	 */
	private final int findX(TreeN t, int state, int k) {
		for(int i = 0;i< state; i++) if(t.getDat(i).intValue()>=k) return i;
		return state-1;
	}
}

Extended Visitors Using Generics

While the switch statement implementation of the extended visitors worked just fine in the above example, the problem with any sort of if-else stack-based implementation, such as a switch statement, is that while it is generally quite fast, it is not very flexible or extensible.  It is quite a common design trade-off where speed trades for flexibility.   In general, delegation-based architectures will give you better flexibility and extensibility, but for smaller, less complex systems, the overhead of the extra method calls may hurt performance.   The break-even point in performance comes when the conditional logic takes as much computing time as a method dispatch.   Beyond that point, delegation-based systems will win on both speed and flexibility counts.

So, instead, we consider this notion:

A visitor is a collection of semantically related lambdas.

That is, overall, a visitor has a certain semantic, a meaning or utility.   Each case of the a visitor is the implementation of that semantic for its associated host.   If we say that a method is nothing more than a lambda function held under the closure created by its class, then why can't we represent a visitor as a physical collection of lambda objects?

If we use a dictionary (java.util.Map) to hold our lambdas, we can associate an index (the key) with a lambda (the value).   The parameterized caseAt method of an extended visitor thus becomes merely a process of taking the given index value, using it to retrieve the associated lambda from the dictionary and then executing that lambda.  If no associated lambda can be found, then simply run a default lambda.

This lambda-based implementation gives us complete flexibility in dynamically manipulating the cases the visitor can handle.   It also fundamentally changes the process of creating a visitor from one of defining cases in code at compile-time to one of loading lambdas at run-time.

Rolling generics into the a lambda-based extended visitor is a non-trivial task.   The current state of the code is a "work in progress" still.

See the HW05 assignment page documentation and code for the latest generic lambda-based extended visitor implementation.

Here's how the above self-balancing tree deletion algorithm changes when implemented with a generic lambda-based extended visitor.    Things to note:

 

/**
 * DeleteNAlgo  -  deletes the supplied parameter from a TreeN
 *  preserving balance with a maximum number of elements per node.
 * 
 * Utilizes lambda-based extended visitors
 */
public class DeleteNAlgo2 extends ATreeNAlgo {
  
  /**
   * Splits child upwards if its state > order and splices it into
   * its parent.  The supplied param is an ISpliceCmd used to do the
   * splicing.
   */
  private SplitUpAndApply splitUpAndSplice;
  
  
  private Comparator comp = new Comparator() {
    public int compare(E o1, E o2) {
      return ((Comparable)o1).compareTo(o2);
    }
  };
  
  /**
   * Constructor for the class
   * @param order -- the max possible number of elements in a node
   */
  public DeleteNAlgo2(int order) {
    System.out.println("DeleteNAlgo2 instantiated.  order = "+order);
    
    splitUpAndSplice = new SplitUpAndApply(order);
    
    setCmd(0, new IExtVisitorCmd>(){      
      public E apply(Integer index, final TreeN host, final E... keys) {
        return keys[0];  // no-op for empty case
      }
    });
    
    setCmd(1, new IExtVisitorCmd>(){      
      public E apply(Integer index, final TreeN host, final E... keys) {
        collapse2Node(host);
        return getDefaultCmd().apply(index, host, keys);  // rest is same as default
      }
    });
    
    setDefaultCmd(new IExtVisitorCmd>(){      
      public E apply(Integer index, final TreeN host, final E... keys) {
        return host.execute(new ATreeNAlgo,TreeN>>() {
          private ATreeNAlgo,TreeN>> this_helper = this;
          
          //Initializer block
          {            
            setCmd(0, new IExtVisitorCmd,TreeN>, TreeN>(){      
              public E apply(Integer index, final TreeN h, final ILambda,TreeN>... cmds) {
                return keys[0];
              }
            });
            
            setCmd(1, new IExtVisitorCmd,TreeN>, TreeN>(){      
              public E apply(Integer index, final TreeN h, final ILambda,TreeN>... cmds) {
                if (h.getDat(0).equals(keys[0])) {
                  //System.out.println("Found data element!");
                  E d = h.getDat(0); // get key
                  h.splitDownAt(0); //transition to empty
                  return d;
                }
                else {
                  //System.err.println("Element "+ key[0] +" not in tree!");
                  cmds[0].apply(h);
                  return h.getDat(0);
                }
              }
            });
            
            setDefaultCmd(new IExtVisitorCmd,TreeN>, TreeN>(){      
              public E apply(Integer s_help, final TreeN h, final ILambda,TreeN>... cmds) {
                final int x = findX(h, s_help, keys[0]);
                TreeN newChild = collapse2Node(h.splitDownAt(x).getChild(x)); // push data down
                E result = newChild.execute(this_helper, new ILambda, TreeN>() {
                  /**
                   * @param child a TreeN subtree of h.
                   */
                  public TreeN apply(TreeN... child) {
                    return h.spliceAt(x, child[0]);
                  }
                });
                h.execute(splitUpAndSplice, cmds);
                return result;
              }
            });
          }
        }, new ILambda, TreeN>() {
          public TreeN apply(TreeN... nu) {
            return host;
          }
        });
      }
    });    
  }
  
  
  //------ Utility methods ------------------------------------------------------
  
  /**
   * Utility method to collapses a 2-node tree by splicing it with
   * its two children.
   * @param t -- must be a 2-node tree!
   */
  private final TreeN collapse2Node(TreeN t) {
    t.spliceAt(1,t.getChild(1));
    t.spliceAt(0,t.getChild(0));
    return t;
  }
  
  /**
   * Utility method that finds the index of the data element that
   * either matches the supplied key or is the first element bigger
   * than the key.
   * @param t - an TreeN
   * @param state - the state of the tree
   * @param k - the key to locate
   * @return - the index of the data element such that if k exists in the
   * tree, it is guaranteed to be in the 2-node tree
   * defined by the data at the index and its left and right children.
   */
  private final int findX(TreeN t, int state, E k) {
    for(int i = 0;i< state; i++) {
      if(0 <= comp.compare(t.getDat(i),k)) return i;
    }
    return state-1;
  }
}

© 2010 by Stephen Wong