package edu.brown.graphs;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections15.set.ListOrderedSet;
import org.apache.log4j.Logger;
import edu.brown.utils.AbstractTreeWalker;
import edu.brown.utils.CollectionUtil;
/**
* @author pavlo
*
*/
public abstract class VertexTreeWalker<V extends AbstractVertex, E extends AbstractEdge> extends AbstractTreeWalker<V> {
private static final Logger LOG = Logger.getLogger(VertexTreeWalker.class);
public enum TraverseOrder {
/** Breadth-First Search */
BREADTH,
/** Depth-First Search */
DEPTH,
/**
* Traverse the graph such that the first callback is for the first element that
* is farthest away from the given root
*/
LONGEST_PATH,
};
public enum Direction {
/** Traverse the DirectedGraph from parent to child */
FORWARD,
/** Traverse the DirectedGraph from child to parent */
REVERSE,
/** Traverse the UndirectedGraph in any direction */
ANY;
public Direction getReverse() {
switch (this) {
case FORWARD:
return (REVERSE);
case REVERSE:
return (FORWARD);
case ANY:
return (ANY);
}
return (null);
}
};
private IGraph<V, E> graph;
private TraverseOrder search_order;
private Direction search_direction;
/**
* Breadth-First Search
* The last element at each depth in the tree. This is where we will still all of the
* children that we need to visit in the next level
*/
private Map<Integer, V> bfs_levels;
protected VertexTreeWalker() {
// Nothing!
}
public VertexTreeWalker(IGraph<V, E> graph, TraverseOrder order, Direction direction) {
this.init(graph, order, direction);
}
public VertexTreeWalker(IGraph<V, E> graph) {
this(graph, TraverseOrder.DEPTH, Direction.FORWARD);
}
public VertexTreeWalker(IGraph<V, E> graph, TraverseOrder order) {
this(graph, order, Direction.FORWARD);
}
public VertexTreeWalker<V, E> init(IGraph<V, E> graph, TraverseOrder order, Direction direction) {
this.graph = graph;
this.search_order = order;
this.search_direction = direction;
if (this.search_order == TraverseOrder.BREADTH && this.bfs_levels == null) {
this.bfs_levels = new HashMap<Integer, V>();
}
return (this);
}
@Override
public void finish() {
super.finish();
if (this.bfs_levels != null) this.bfs_levels.clear();
}
public final IGraph<V, E> getGraph() {
return this.graph;
}
protected Collection<V> getNext(Direction direction, V element) {
Collection<V> next = null;
switch (direction) {
case FORWARD:
next = this.graph.getSuccessors(element);
break;
case REVERSE:
next = this.graph.getPredecessors(element);
break;
case ANY:
next = this.graph.getNeighbors(element);
break;
default:
assert(false) : "Unexpected search direction: " + direction;
} // SWITCH
return (next);
}
/* (non-Javadoc)
* @see org.voltdb.utils.AbstractTreeWalker#traverse_children(java.lang.Object)
*/
@Override
protected void populate_children(VertexTreeWalker.Children<V> children, V element) {
final boolean trace = LOG.isTraceEnabled();
ListOrderedSet<V> bfs_children = null;
if (trace) LOG.trace("Populating Children for " + element + " [direction=" + this.search_direction + "]");
for (V child : this.getNext(this.search_direction, element)) {
if (!this.hasVisited(child)) {
switch (this.search_order) {
case DEPTH:
children.addAfter(child);
break;
case BREADTH:
if (bfs_children == null) bfs_children = new ListOrderedSet<V>();
bfs_children.add(child);
break;
case LONGEST_PATH: {
// Check whether the parents of this vertex have already been visited
// If they have, then yeah we'll go ahead and visit it now
boolean all_visited = true;
for (V parent : this.getNext(this.search_direction.getReverse(), child)) {
if (!element.equals(parent) && !this.hasVisited(parent)) {
all_visited = false;
break;
}
} // FOR
if (all_visited) children.addAfter(child);
break;
}
default:
assert(false) : "Unimplemented TraverseOrder: " + this.search_order;
} // SWITCH
}
} // FOR
// Special Case: Breadth-First Search
if (this.search_order == TraverseOrder.BREADTH && bfs_children != null && bfs_children.isEmpty() == false) {
if (element.equals(this.getFirst())) {
children.addAfter(bfs_children);
// Otherwise we need attach all of the children to the last element at this depth
// We want to make sure that if there is already a last element that we remove all of their
// children and put it into our new last element
} else {
V last_element = this.bfs_levels.get(this.getDepth());
assert(last_element != null) : "Null last_element at depth " + this.getDepth();
// Put all of our children into the last element's in our depth children
Children<V> last_element_c = this.getChildren(last_element);
for (V child : bfs_children) {
// We have to do this to avoid duplicates...
if (last_element_c.getAfter().contains(child) == false) last_element_c.addAfter(child);
} // FOR
if (trace) LOG.trace("BFS Last Element [depth=" + this.getDepth() + "]: " + last_element + " => " + last_element_c.getAfter());
}
// Always mark our last child as the next child in the next depth
this.bfs_levels.put(this.getDepth() + 1, CollectionUtil.last(bfs_children.asList()));
}
return;
}
}