5

I'm trying to find a way that I could take a binary tree class and traverse through its nodes,
performing X amount of inline actions on each node without having to rewrite the same traversal code over and over again.
I'd imagine if Java allowed function pointers, this would be much simpler for me to figure out...

Basically, what I need is something like the following:

public class BinaryTreeNode {

    //...

    public void inOrderTraversalFrom(BinaryTreeNode node, /* ??? */ actions) {
        if(node.left != null)
            inOrderTraversalFrom(node.left);

        /* do something with "actions" here */

        if(node.right != null)
            inOrderTraversalFrom(node.right);
    }
}


... where actions could allow for different methods to be performed, taking in a different set of parameters depending on the type of action(s) to be performed on each node.

A good example of this would be a class which is meant to draw these nodes could be passed, accepting a Graphics object as one of it's parameters, versus a class which is meant to perform some other series of actions which wouldn't require a Graphics object as a parameter, but a completely different set of parameters instead.

How is this possible? What would be the most dynamic way to perform this?

duffymo
  • 305,152
  • 44
  • 369
  • 561
RectangleEquals
  • 1,825
  • 2
  • 25
  • 44
  • 2
    http://stackoverflow.com/questions/122407/whats-the-nearest-substitute-for-a-function-pointer-in-java – Mitch Wheat Mar 09 '13 at 04:01
  • @MitchWheat very interesting, i'll have to use this sometime... but it still doesn't solve the problem of having to rewrite the same `inOrderTraversalFrom` method to accept different interfaces. Perhaps the use of generics could somehow be combined here? – RectangleEquals Mar 09 '13 at 04:19

3 Answers3

1

One way to do it would be:

action could be an instance of a class that receives a Node and applies the Action to it:

public interface Action{
  public void apply(Node node){
  // To be done in the classes that will implement 
  // this interface. If it's a graphic-display of the nodes, the apply 
  // method will call draw(node.value), for example
  }
}

The line:

/* do something with "actions" here */

should be replaced with

action.apply(node);

The signature of the method should be changed to:

public void inOrderTraversalFrom(BinaryTreeNode node, Action action)

and the recursive call should be:

inOrderTraversalFrom(node.left, action);
Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129
  • This was very close to what I was going for, but does not accept a varying set of parameters for a certain action to be performed. – RectangleEquals Mar 09 '13 at 04:49
  • The answer you posted is very close to my suggestion: Preform is another name to Apply. The only difference is that your solution offers another layer of abstraction which is needed when you want to use external libraries in your solution (something which wasn't mentioned in the question). As long as you implement the graphic actions - you can implement the `apply()` method directly in your class and you don't need this additional level of abstraction. – Nir Alfasi Mar 09 '13 at 05:04
  • I was going off of the link provided by Mitch Wheat earlier about function pointers in Java. Our code is related by coincidence, since I posted mine before refreshing the page. And I do understand what you mean now, but it does not allow for actions to be performed _inline_ (something that my question _does_ ask). For example, if `BinaryTreeView` were to implement `BinaryTreeActions`, how would I pass the _Graphics_ object to `DrawNode`? There wouldn't be one to pass until it is already passed to `BinaryTreeView.DrawNodes`, since DrawNodes is called from an overridden `paint` method... – RectangleEquals Mar 09 '13 at 05:23
  • 1
    @RectangleEquals if I understand correctly you just provided a second and very good reason for adding another layer of abstraction: if by "inline" you mean that you want to cascade methods (i.e. `g.set(node).color('red').setX(something).setY(somethingElse)...` ) then by all means YES, it totally makes sense to add another layer of abstraction. Good job! – Nir Alfasi Mar 09 '13 at 05:37
  • By _inline_, I was referring to creating the body of actions "on the fly" from within an already executing block of code. Refer to `BinaryTreeView.java` for an example of this. Doing it this way allows variables from a local scope to be passed in (provided they are made _final_), which would be a requirement in this case since _DrawNodes_ takes a Graphics object as a parameter, and is called from an overridden _paint_ method to supply a valid Graphics object. If I did it your way, I would need to place the call to `inOrderTraversal` inside the paint method and do away completely with DrawNodes – RectangleEquals Mar 09 '13 at 05:54
  • Ah wait... I just realized I could actually just do away with _BinaryTreeGraphicsActions_ completely and use: `root.traverse(node, new BinaryTreeActions() { @Override public void Perform(BinaryTreeNode node) { /* Draw node using graphics object */ } });` ... so +1 to you for getting into my thick skull – RectangleEquals Mar 09 '13 at 06:02
  • 1
    Yes, that's what I meant when I said that you don't need another level of abstraction ;) But, if you would have to use an `external` BinaryTreeActions that doesn't "know" how to work with Nodes, then you would need to extend it (create another level of abstraction that knows how to connect graphics and nodes) like you originally suggested – Nir Alfasi Mar 09 '13 at 06:14
  • edited my answer to clarify why I chose this one instead. Thanks! – RectangleEquals Mar 09 '13 at 06:15
1

I found a solution, though it might not be the best...

BinaryTreeNode.java:

public void inOrderTraversalFrom(BinaryTreeNode rootNode, BinaryTreeActions actions)
{
    if(rootNode.left != null)
        inOrderTraversalFrom(rootNode.left, actions);

    if(actions != null)
        actions.Perform(rootNode);

    if(rootNode.right != null)
        inOrderTraversalFrom(rootNode.right, actions);
}


BinaryTreeActions.java:

public interface BinaryTreeActions {
    public void Perform(BinaryTreeNode node);
}


BinaryTreeGraphicsActions.java:

public interface BinaryTreeGraphicsActions extends BinaryTreeActions {
    void DrawNode(BinaryTreeNode node, Graphics g);
}


BinaryTreeView.java:

private void DrawNodes(final Graphics graphics)
{
    BinaryTreeNode node = root;
    root.inOrderTraversalFrom(node, new BinaryTreeGraphicsActions() {
        @Override
        public void DrawNode(BinaryTreeNode node, Graphics g) {
            // draw the node
        }

        @Override
        public void Perform(BinaryTreeNode node) {
            DrawNode(node, graphics);
        }
    });
}


...and any time you need a new set of actions, you would create a new interface for it, following the same idea in BinaryTreeGraphicsActions and BinaryTreeView. This allows you to perform any set of actions, depending on the interface you have designed for them.


#EDIT : I accepted a similar answer after discovering there is no need for BinaryTreeGraphicsActions, since you can do the same inline code simply using BinaryTreeActions like so:

private void DrawNodes(final Graphics graphics)
{
    BinaryTreeNode node = root;
    root.inOrderTraversalFrom(node, new BinaryTreeActions() {
        @Override
        public void Perform(BinaryTreeNode node) {
            /* draw the node, using any local vars (providing they are final) */
        }
    });
}
RectangleEquals
  • 1,825
  • 2
  • 25
  • 44
0

Following solution is pretty similar to others suggested here. But with some design pattern icing if you are into these things.

The idea is to segregate not only actions but also traversal. This can be done with following 2 abstractions.

  • BinaryTreeNodeTraversalStrategy
  • BinaryTreeNodeVisitor


public interface Visitable<T extends Visitor> {

  public void accept(T visitor);
}

public interface Visitor<T extends Visitable> {

  public void visit(T visitable);
}

public interface BinaryTreeNode implements Visitable<BinaryTreeNodeVisitor> {

  public void accept(BinaryTreeNodeVisitor visitor);
}

public interface BinaryTreeNodeVisitor implements Visitor<BinaryTreeNode> {

  public void visit(BinaryTreeNode visitable);
}

public interface BinaryTreeNodeTraversalStrategy {

  public void traverse(BinaryTreeNode root);
}
sgp15
  • 1,280
  • 8
  • 13