Java tutorial
/** * Copyright (c) 2008-2010 Morten Silcowitz. * * This file is part of the Jinngine physics library * * Jinngine is published under the GPL license, available * at http://www.gnu.org/copyleft/gpl.html. */ //package jinngine.util; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; /** * An undirected graph that keeps track of connected components (groups). Each * time an edge is added or removed from the graph, data structures are * maintained, reflecting connected components in the graph. This means, that * adding edges are roughly an O(k) operation, while removing an edge could i * result in a total traversal of the graph, visiting all present edges, worst * case O((n-1)^2) where n is the number of nodes. Usually this will be much * cheaper, given that the graph has a low density, and is fragmented into * separated components. * * @param <T> * Type that stores in nodes * @param <U> * Type that stores in edges */ public class HashMapComponentGraph<T, U, V> implements ComponentGraph<T, U, V> { // wrapping classes private final class Node { public Node(T element) { this.element = element; } public final T element; public int color; public final int hashCode() { return element.hashCode(); } public final boolean equals(Object other) { return element.equals(((Node) other).element); } } // component wrapper class for the type V. private final class Component { private final V element; public Component(V element) { this.element = element; } public final int hashCode() { return element.hashCode(); } public final boolean equals(Object other) { return element.equals(((Component) other).element); } } // /** // * Node classifier for the ContactGraph // * // * @param <T> Type that stores in nodes // */ // public interface NodeClassifier<T> { // /** // * @param node Node to classify // * @return true if the node is to be considered as a delimiting node, such // that two // * components in some graph, would not be merged if connected through such // a node. Returns false otherwise. // */ // public boolean isDelimitor(final T node); // } // this would ideally be a Set, but the HashSet implementation doesn't allow // one to get the // actual reference to a specific object in the set. This means that we // can't keep our Node objects // unique, which we would like to do private final LinkedHashMap<Node, Node> allnodes = new LinkedHashMap<Node, Node>(); private final LinkedHashSet<Node> freenodes = new LinkedHashSet<Node>();// private final Map<Node, Set<Node>> edges = new LinkedHashMap<Node, Set<Node>>(); private final Map<Node, Component> component = new LinkedHashMap<Node, Component>(); private final Map<Pair<T>, U> edgeData = new LinkedHashMap<Pair<T>, U>();// private final Map<Component, Set<Node>> componentNodes = new LinkedHashMap<Component, Set<Node>>();// private final Map<Component, Set<Pair<T>>> componentEdges = new LinkedHashMap<Component, Set<Pair<T>>>();// private final NodeClassifier<T> nodeClassifier; // TODO the nodes map is not cleaned up, nodes that are removed still // remains in here // default component creator private final ComponentHandler<T, V> componenthandler; // = new ComponentHandler<V>() { // public V newComponent() { // return null; // }; // }; // /** // * Create a new component graph // * @param nodeClassifier a classifier for the type T, used for the // connected components analysis // */ // public HashMapComponentGraph( NodeClassifier<T> nodeClassifier ) { // this.nodeClassifier = nodeClassifier; // } /** * Create a new component graph * * @param nodeClassifier * a classifier for the type T, used for the connected components * analysis * @param componetcreator * a creator for new components that arrise inside the component * graph */ public HashMapComponentGraph(NodeClassifier<T> nodeClassifier, ComponentHandler<T, V> componentcreator) { this.componenthandler = componentcreator; this.nodeClassifier = nodeClassifier; } /** * Add an edge to the graph, and implicitly add included end-nodes if not * already present in the graph. This is roughly an O(k) and sometimes * O(nodes) operation, depending on whether components are to be merged or * not. * * @param pair * A pair of nodes, where an edge is to be added between them. * @param edgeelement * An element of type U to store in the new edge */ @Override public final void addEdge(Pair<T> pair, U edgeelement) { // do not act if edge is already present if (edgeData.containsKey(pair)) { // update the edge data user reference edgeData.put(pair, edgeelement); // System.out.println("Edge already present"); return; } // add the new edge data to the edge edgeData.put(pair, edgeelement); // get nodes from the node tables ( in this way we can keep data other // than T in the Node objects) // if the nodes are not present, we add them to the allnodes and to // freenodes Node a = new Node(pair.getFirst()); if (allnodes.containsKey(a)) { a = allnodes.get(a); } else { allnodes.put(a, a); freenodes.add(a); } Node b = new Node(pair.getSecond()); if (allnodes.containsKey(b)) { b = allnodes.get(b); } else { allnodes.put(b, b); freenodes.add(b); } // if b is fixed, interchange a and b ( now, if b is fixed, both a and b // are fixed) if (nodeClassifier.isDelimitor(b.element)) { Node t = a; a = b; b = t; } // add edge to nodes. First create hash sets, and then add the nodes to // them if (!edges.containsKey(a)) edges.put(a, new LinkedHashSet<Node>()); if (!edges.containsKey(b)) edges.put(b, new LinkedHashSet<Node>()); edges.get(b).add(a); edges.get(a).add(b); // Cases // i. Both nodes are delimiters // a) do nothing // ii. One node is delimiter: // a). non-delimiter is in a component // do nothing // b). non-delimiter is not in a component // create a new component for the non-delimiter node // iii. No node is delimiter: // a). no node is in a component // create a new component for both nodes // b). one node is in a component // add the new node to this component // c). both nodes are in a component // 1. the same component // do nothing // 2. different components // merge the one component into the other // case i a) // both a and b are delimitors if (nodeClassifier.isDelimitor(b.element)) { // do nothing // ii) // one is delimiter } else if (nodeClassifier.isDelimitor(a.element)) { // if b is not in a group, create a new one for b if (!component.containsKey(b)) { // case ii b) Component g = new Component(componenthandler.newComponent()); // add b to the new group component.put(b, g); componentNodes.put(g, new LinkedHashSet<Node>()); componentNodes.get(g).add(b); // notify handler componenthandler.nodeAddedToComponent(g.element, b.element); // b is not a free node anymore freenodes.remove(b); // add to pairs componentEdges.put(g, new LinkedHashSet<Pair<T>>()); componentEdges.get(g).add(pair); // return; } else { // case ii a) // add to pairs Component g = component.get(b); componentEdges.get(g).add(pair); } // non of the bodies are delimiters } else { // if b is in a group, interchange a and b // ( now, if b is in a group, both a and b are grouped) if (component.containsKey(b)) { Node t = a; a = b; b = t; } // both in components if (component.containsKey(b)) { // same component if (component.get(a) == component.get(b)) { // do nothing // add edge to this component componentEdges.get(component.get(a)).add(pair); // different components } else { // two nodes from two different components was connected. // we then merge the two components into one // merge groups (remove the gb group) Component ga = component.get(a); Component gb = component.get(b); // call the user handler to say we are merging gb into ga componenthandler.mergeComponent(ga.element, gb.element); // update the component table, i.e. update bodies that was // tied to component gb, to ga Iterator<Node> i = componentNodes.get(gb).iterator(); while (i.hasNext()) { Node body = i.next(); component.put(body, ga); } // put nodes in group b into group a componentNodes.get(ga).addAll(componentNodes.get(gb)); componentEdges.get(ga).addAll(componentEdges.get(gb)); // also, add the new edge (pair) componentEdges.get(ga).add(pair); // remove the gb component from the component table componentNodes.remove(gb); componentEdges.remove(gb); // return; } // one group } else if (component.containsKey(a)) { // assign b to the group of a Component g = component.get(a); component.put(b, g); componentNodes.get(g).add(b); componentEdges.get(g).add(pair); // b is not a free node anymore freenodes.remove(b); // notify handler that b is added to the group of a componenthandler.nodeAddedToComponent(g.element, b.element); // return; // no groups } else { // create a new component for both bodies Component newGroup = new Component(componenthandler.newComponent()); component.put(a, newGroup); component.put(b, newGroup); componentNodes.put(newGroup, new LinkedHashSet<Node>()); componentNodes.get(newGroup).add(a); componentNodes.get(newGroup).add(b); // notify handler that a and b is added to the new component componenthandler.nodeAddedToComponent(newGroup.element, a.element); componenthandler.nodeAddedToComponent(newGroup.element, b.element); componentEdges.put(newGroup, new LinkedHashSet<Pair<T>>()); componentEdges.get(newGroup).add(pair); // both a and b are not free now freenodes.remove(a); freenodes.remove(b); // return; } } // // System.out.println("After add: " + groups.keySet().size() + // " groups with " + group.size() + " bodies" ); // Iterator<Component> groupiter = componentNodes.keySet().iterator(); // // Set<Pair<T>> allpairs = new HashSet<Pair<T>>(); // Set<Node> allnodes = new HashSet<Node>(); // while(groupiter.hasNext()){ // Component g = groupiter.next(); // //System.out.println( "Group " + g + " : " + groupPairs.get(g).size() // + " pairs " ); // // Iterator<Pair<T>> pairiter = componentEdges.get(g).iterator(); // while (pairiter.hasNext()) { // Pair<T> thispair = pairiter.next(); // //System.out.println( " pair:"+thispair.hashCode()); // if (allpairs.contains(thispair)) { // System.out.println("Duplicates!!!!"); // System.exit(0); // } // allpairs.add(thispair); // // } // // // Iterator<Node> nodeiter = componentNodes.get(g).iterator(); // while (nodeiter.hasNext()) { // Node node = nodeiter.next(); // //System.out.println( " Node:"+node); // if (allnodes.contains(node)) { // System.out.println("Duplicates!!!!"); // System.exit(0); // } // allnodes.add(node); // // } // // // // } } /** * Remove an edge. If the removal results in one or more isolated nodes, * these will be removed from the graph implicitly. * * For non-dense and relatively fragmented graphs, this operation will be * cheap. Otherwise, for dense and strongly connected graphs, the operation * could include a full traversal of the graph visiting all present edges, * resulting in an O((n-1)^2) operation, where n is the number of nodes in * the graph. * * @param pair * edge to be removed * @return true if the edge was actually removed, false if the edge did not * exists before call. */ @Override public final boolean removeEdge(Pair<T> pair) { // don't act if edge is not present if (!edgeData.containsKey(pair)) { // System.out.println("Edge NOT present"); return false; } else { edgeData.remove(pair); } // get the node out of the node adjacency hash map ( at this point we // know that the nodes must // exist, because the edge exists Node a = new Node(pair.getFirst()); if (allnodes.containsKey(a)) { a = allnodes.get(a); } else { // not possible throw new IllegalStateException( "ComponentGraph.removeEdge(): Node did not have an adjacency entry. ComponentGraph corrupted."); } Node b = new Node(pair.getSecond()); if (allnodes.containsKey(b)) { b = allnodes.get(b); } else { // this is not possible throw new IllegalStateException( "ComponentGraph.removeEdge(): Node did not have an adjacency entry. ComponentGraph corrupted."); } // if b is fixed, interchange a and b ( now, if b is fixed, both a and b // are fixed) if (nodeClassifier.isDelimitor(b.element)) { Node t = a; a = b; b = t; } // remove references to each node, in each nodes // connected node sets edges.get(a).remove(b); edges.get(b).remove(a); // if no edges left in set, remove the set if (edges.get(a).isEmpty()) edges.remove(a); // if no edges left in set, remove it if (edges.get(b).isEmpty()) edges.remove(b); // Cases // i. Both node are delimiters // do nothing // ii. One node is delimiter: // a). non-delimiter is in a component // do nothing (node could now be alone in its component) // if node contains no other edges, delete it from its component // b). non-delimiter is not in a component (not possible) // do nothing/ report fatal error // iii. No node is delimiter: // a). no node is in a component (not possible) // do nothing/ error // b). one node is in a component (not possible) // do nothing // c). both nodes are in a component // 1. the same component // remove edge, traverse breadth-first from each node to determine // if component should be split. // 2. different components (not possible) // do nothing/ error // both nodes are fixed if (nodeClassifier.isDelimitor(b.element)) { // do nothing // return; // one is fixed } else if (nodeClassifier.isDelimitor(a.element)) { if (component.containsKey(b)) { // only possible option // System.out.println("One fixed node"); Component g = component.get(b); // check for another edge on this node if (!edges.containsKey(b)) { // System.out.println("b did not have any edges"); // remove the node from component component.remove(b); // notify handler componenthandler.nodeRemovedFromComponent(g.element, b.element); // b is now free freenodes.add(b); Set<Node> s = componentNodes.get(g); if (!s.remove(b)) { System.out.println("ALARM"); System.exit(0); } // remove group if empty if (s.isEmpty()) { // System.out.println("groups entry removed"); componentNodes.remove(g); // TODO notify handler } else { System.out.println("Group isn't empty, why??"); // System.exit(0); } } else { // b has edges left, and is part of a group. Were done } // remove edge from component (even if b was not removed from // the group) Set<Pair<T>> sp = componentEdges.get(g); sp.remove(pair); // remove group if empty if (sp.isEmpty()) { // System.out.println("grouppair entry removed " + g ); componentEdges.remove(g); } } else { throw new IllegalStateException( "HashMapComponentGraph.removeEdge(): A connected non-delimiter node was not in a component. ComponentGraph corrupted."); } // return; // none is fixed } else { // if b has edges, interchange a and b // ( now, if b has edges, both a and b have edges) if (edges.containsKey(b)) { Node t = a; a = b; b = t; } // both are in the same group (only possible option) Component oldgroup = component.get(a); if (oldgroup != component.get(b)) { System.out.println("Different groups??!"); System.exit(0); } // both have edges if (edges.containsKey(b)) { final int NONE = 0; final int RED = 1; final int BLUE = 2; // clear node colors in entire group Iterator<Node> i = componentNodes.get(oldgroup).iterator(); while (i.hasNext()) { i.next().color = NONE; } // perform breadth-first traversal, // to determine if group has become disjoint boolean disjoint = true; Queue<Node> queue = new LinkedList<Node>(); Set<Pair<T>> blueEdges = new LinkedHashSet<Pair<T>>(); a.color = RED; b.color = BLUE; queue.add(a); queue.add(b); // traverse while (!queue.isEmpty()) { Node node = queue.poll(); // add nodes neighbors to queue Iterator<Node> neighbors = edges.get(node).iterator(); while (neighbors.hasNext()) { Node neighbor = neighbors.next(); // remember visited edges if (node.color == BLUE) blueEdges.add(new Pair<T>(node.element, neighbor.element)); if (nodeClassifier.isDelimitor(neighbor.element)) { // ignore fixed nodes continue; } else if (neighbor.color == NONE) { neighbor.color = node.color; queue.add(neighbor); continue; } else if (neighbor.color != node.color) { // group is connected disjoint = false; break; } else { // already visited continue; } } // while neighbors } // while queue // handle result of traversal if (disjoint) { // System.out.println("Splitting group"); // new group Component newgroup = new Component(componenthandler.newComponent()); Set<Node> blues = new LinkedHashSet<Node>(); // find all blue nodes Iterator<Node> iter = componentNodes.get(oldgroup).iterator(); while (iter.hasNext()) { Node node = iter.next(); if (node.color == BLUE) { blues.add(node); component.put(node, newgroup); } } // impossible if (blues.isEmpty()) { System.out.println("Why was no blue nodes found?"); System.exit(0); } // remove bodies from old components and add the new // component componentNodes.get(oldgroup).removeAll(blues); componentNodes.put(newgroup, blues); // remove blue edges from the red group and create a new // group with pairs (ng) componentEdges.get(oldgroup).removeAll(blueEdges); componentEdges.get(oldgroup).remove(pair); // the edge that // was to be // removed componentEdges.put(newgroup, blueEdges); // return; } else { // System.out.println("Group still connected"); // we keep group as it is, but remove the pair (edge) Set<Pair<T>> sp = componentEdges.get(oldgroup); sp.remove(pair); // remove group if empty if (sp.isEmpty()) { // System.out.println("grouppair entry removed " + // oldgroup ); componentEdges.remove(oldgroup); } // return; } // a has an edge and b do not } else if (edges.containsKey(a)) { // keep group as it is, but wipe out b component.remove(b); componentNodes.get(oldgroup).remove(b); // b is now a free node freenodes.add(b); // notify handler that b is removed from oldgroup componenthandler.nodeRemovedFromComponent(oldgroup.element, b.element); if (componentNodes.get(oldgroup).isEmpty()) { // never happens System.out.println("How can group be empty?"); componentNodes.remove(oldgroup); } // remove from pairs // System.out.println("removing " + pair +" from group pairs " + // oldgroup); Set<Pair<T>> sp = componentEdges.get(oldgroup); sp.remove(pair); // remove group if empty if (sp.isEmpty()) { // System.out.println("grouppair entry removed " + oldgroup // ); componentEdges.remove(oldgroup); } // non have edges } else { // clear out group entirely component.remove(a); component.remove(b); // both a and b are free nodes now freenodes.add(a); freenodes.add(b); // notify handler that a and b is removed componenthandler.nodeRemovedFromComponent(oldgroup.element, a.element); componenthandler.nodeRemovedFromComponent(oldgroup.element, b.element); // assume that the group is only containing a and b? componentNodes.get(oldgroup).remove(b); componentNodes.get(oldgroup).remove(a); if (componentNodes.get(oldgroup).isEmpty()) { componentNodes.remove(oldgroup); } else { // impossible System.out.println("Hmm still stuff in group but no outgoing edges?" + componentNodes.get(oldgroup) + " a and b is " + a + ", " + b); System.exit(0); } // remove from pairs Set<Pair<T>> sp = componentEdges.get(oldgroup); sp.remove(pair); // remove group if empty if (sp.isEmpty()) { // System.out.println("grouppair entry removed " + oldgroup // ); componentEdges.remove(oldgroup); } } // non have edges } // none is fixed // System.out.println("After remove: " + groups.keySet().size() + // " groups with " + group.size() + " bodies" ); // Iterator<Component<V>> groupiter = // componentNodes.keySet().iterator(); // // Set<Pair<T>> allpairs = new HashSet<Pair<T>>(); // Set<Node> allnodes = new HashSet<Node>(); // while(groupiter.hasNext()){ // Component<V> g = groupiter.next(); // //System.out.println( "Group " + g + " : " + groupPairs.get(g).size() // + " pairs " ); // // Iterator<Pair<T>> pairiter = componentEdges.get(g).iterator(); // while (pairiter.hasNext()) { // Pair<T> thispair = pairiter.next(); // //System.out.println( " pair:"+thispair.hashCode()); // if (allpairs.contains(thispair)) { // System.out.println("Duplicates!!!!"); // System.exit(0); // } // allpairs.add(thispair); // // } // // // Iterator<Node> nodeiter = componentNodes.get(g).iterator(); // while (nodeiter.hasNext()) { // Node node = nodeiter.next(); // //System.out.println( " Node:"+node); // if (allnodes.contains(node)) { // System.out.println("Duplicates!!!!"); // System.exit(0); // } // allnodes.add(node); // // } // // } return true; } @Override public final U getEdge(Pair<T> pair) { if (edgeData.containsKey(pair)) { return edgeData.get(pair); } else { return null; } } @Override public final Iterator<V> getComponents() { // wrapping iterator return new Iterator<V>() { private final Iterator<Component> iter = componentEdges.keySet().iterator(); public boolean hasNext() { return iter.hasNext(); } @Override public V next() { return iter.next().element; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public final Iterator<U> getEdgesInComponent(V c) { // get edges from component final Set<Pair<T>> edges = componentEdges.get(new Component(c)); // abort if the component doesn't exist if (edges == null) return null; // get the edges final Iterator<Pair<T>> i = edges.iterator(); // create an iterator that wraps the process of picking out the // edge data types from edgeData return new Iterator<U>() { @Override public boolean hasNext() { return i.hasNext(); } @Override public U next() { if (i.hasNext()) { Pair<T> p = i.next(); // return the edge data return edgeData.get(p); } // no element available return null; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public Iterator<T> getNodesInComponent(V c1) { // get edges from component final Set<Node> nodes = componentNodes.get(new Component(c1)); // abort if the component doesn't exist if (nodes == null) return null; // get the edges final Iterator<Node> i = nodes.iterator(); // create an iterator iterates the nodes, but return the T element value return new Iterator<T>() { @Override public boolean hasNext() { return i.hasNext(); } @Override public T next() { if (i.hasNext()) { Node p = i.next(); // return the node data return p.element; } // no element available return null; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } /** * Auxiliary method to print the graph */ public final void print() { System.out.println("Status: " + componentNodes.keySet().size() + " components with " + component.size() + " bodies, " + getNumberOfFreeNodes() + " free "); Iterator<Component> groupiter = componentNodes.keySet().iterator(); Set<Pair<T>> allpairs = new LinkedHashSet<Pair<T>>(); Set<Node> allnodes = new LinkedHashSet<Node>(); while (groupiter.hasNext()) { Component g = groupiter.next(); System.out.println("Group " + g.element + " : " + componentEdges.get(g).size() + " pairs, " + componentNodes.get(g).size() + " nodes "); Iterator<Pair<T>> pairiter = componentEdges.get(g).iterator(); while (pairiter.hasNext()) { Pair<T> thispair = pairiter.next(); // System.out.println( " pair:"+thispair.hashCode()); if (allpairs.contains(thispair)) { System.out.println("Duplicates!!!!"); System.exit(0); } allpairs.add(thispair); } Iterator<Node> nodeiter = componentNodes.get(g).iterator(); while (nodeiter.hasNext()) { Node node = nodeiter.next(); // System.out.println( " Node:"+node); if (allnodes.contains(node)) { System.out.println("Duplicates!!!!"); System.exit(0); } allnodes.add(node); } } } @Override public int getNumberOfComponents() { // return the number of keys in the component-Nodes map return componentNodes.keySet().size(); } @Override public Iterator<T> getConnectedNodes(final T node) { // create a wrap iterator return new Iterator<T>() { Iterator<Node> i = edges.get(new Node(node)).iterator(); @Override public boolean hasNext() { return i.hasNext(); } @Override public T next() { return i.next().element; } @Override public void remove() { } }; } public void addNode(T nodeelement) { // check if we know about this node Node node = new Node(nodeelement); if (allnodes.containsKey(node)) { // ignore. Maybe give warning? System.out.println("HashMapComponentGraph.addNode(): Node is already in graph"); } else { // add to both allnodes and freenodes allnodes.put(node, node); freenodes.add(node); } } public void removeNode(T nodeelement) { Node node = new Node(nodeelement); // check if node is in freenodes. If so, we don't have to do anything // but removing the node // from both freenodes and allnodes if (freenodes.contains(node)) { freenodes.remove(node); allnodes.remove(node); return; } // we now expect node to be part of a component, but we can now simply // remove all incident // edges to the node. After that, the node will be placed into // freenodes, and we can trivially // remove it // remove each incident edges. store edges in intermediate list to avoid // concurrent modification errors List<Pair<T>> edgesToRemove = new ArrayList<Pair<T>>(); Iterator<T> neighbors = getConnectedNodes(node.element); while (neighbors.hasNext()) { Pair<T> nodepair = new Pair<T>(node.element, neighbors.next()); edgesToRemove.add(nodepair); } for (Pair<T> edge : edgesToRemove) removeEdge(edge); // we can now remove the node if (freenodes.contains(node)) { freenodes.remove(node); allnodes.remove(node); return; } else { System.out.println("HashMapComponentGraph.removeNode(): Node was not free after removing its edges"); } } @Override public int getNumberOfNodes() { return allnodes.size(); } @Override public int getNumberOfFreeNodes() { return freenodes.size(); } @Override public Iterator<T> getFreeNodes() { // wrapping iterator return new Iterator<T>() { Iterator<Node> i = freenodes.iterator(); @Override public boolean hasNext() { return i.hasNext(); } @Override public T next() { return i.next().element; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public Iterator<U> getConnectedEdges(final T node) { if (edges.containsKey(new Node(node))) { // create a wrap iterator return new Iterator<U>() { Set<Node> outEdges = edges.get(new Node(node)); Iterator<Node> i = outEdges.iterator(); @Override public boolean hasNext() { return i.hasNext(); } @Override public U next() { // create a edge pair from the node types, and return the // edge data return edgeData.get(new Pair<T>(node, i.next().element)); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } else { // create a dummy empty iterator return new Iterator<U>() { @Override public boolean hasNext() { return false; } @Override public U next() { return null; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } } } /** * Copyright (c) 2008-2010 Morten Silcowitz. * * This file is part of the Jinngine physics library * * Jinngine is published under the GPL license, available at * http://www.gnu.org/copyleft/gpl.html. */ // package jinngine.util; // import java.util.Iterator; /** * An undirected graph that keeps track of connected components (groups). Each * time an edge is added or removed from the graph, data structures are * maintained, reflecting connected components in the graph. This means, that * adding edges are roughly an O(k) operation, while removing an edge could i * result in a total traversal of the graph, visiting all present edges, worst * case O((n-1)^2) where n is the number of nodes. Usually this will be much * cheaper, given that the graph has a low density, and is fragmented into * separated components. * * @param <T> * Type that stores in nodes * @param <U> * Type that stores in edges * @param <V> * Type that stores in components */ interface ComponentGraph<T, U, V> { /** * Interface for supplying custom component objects. Components are used to * reference the independently connected components in the graph. It is * therefore useful to be able to store user information within the * component references. */ public interface ComponentHandler<T, V> { /** * Called when a new component is created. This call MUST return a * unique object of type V, that has not been previously known to the * ComponentGraph * * @return a new unique component of type V */ public V newComponent(); /** * Called prior to two components being merged. This gives the user a * way to manipulate data in the component type V. During this call, it * is possible to access the data of the ComponentGraph in a read-only * fashion. * * @param remaining * The component that will be the union of both components * after merge * @param disappearing * The component that will be removed from the graph */ public void mergeComponent(V remaining, V leaving); /** * Called when a node is added to some component */ public void nodeAddedToComponent(V component, T node); /** * Called when a node is removed from some component */ public void nodeRemovedFromComponent(V component, T node); } /** * Node classifier for the ContactGraph * * @param <T> * Type that stores in nodes */ public interface NodeClassifier<T> { /** * @param node * Node to classify * @return true if the node is to be considered as a delimiting node, * such that two components in some graph, would not be merged * if connected through such a node. Returns false otherwise. */ public boolean isDelimitor(final T node); } /** * Add a node to the graph. If the node already exists in the graph, the * call will have no effect. * * @param node */ public void addNode(T node); /** * Remove a node from the graph. All edges incident to this node will be * removed as well. * * @param node */ public void removeNode(T node); /** * Add an edge to the graph, and implicitly add included end-nodes if not * already present in the graph. This is roughly an O(k) and sometimes * O(nodes) operation, depending on whether components are to be merged or * not. * * @param pair * A pair of nodes, where an edge is to be added between them. * @param edgeelement * An element of type U to store in the new edge */ public void addEdge(Pair<T> pair, U edgeelement); /** * Remove an edge. If the removal results in one or more isolated nodes, * these will be removed from the graph implicitly. * * For non-dense and relatively fragmented graphs, this operation will be * cheap. Otherwise, for dense and strongly connected graphs, the operation * could include a full traversal of the graph visiting all present edges, * resulting in an O((n-1)^2) operation, where n is the number of nodes in * the graph. * * @param pair * edge to be removed * @return true if the edge was actually removed, false if the edge did not * exists before call. */ public boolean removeEdge(Pair<T> pair); /** * Get the edge element of type U that is stored in the edge defined by a * pair of node types T. If no such edge exist, the return value is null. * * @param pair * A pair of T type objects defining an edge in the graph * @return The U type object stored in the edge. Return value is null if no * such edge is present in the graph */ public U getEdge(Pair<T> pair); /** * Return an iterator yielding the edges in the specified component. * * @param c * Component to iterate * @return Iterator giving the edge elements in the component */ public Iterator<U> getEdgesInComponent(V c); /** * Returns an iterator yielding the nodes present in the given component * * @param c * Any component of this graph * @return An iterator yielding the nodes present in the component c */ public Iterator<T> getNodesInComponent(V c); /** * Return an iterator that yields the components in the graph * * @return */ public Iterator<V> getComponents(); /** * Return the number of components in this graph */ public int getNumberOfComponents(); /** * Return the total number of nodes in this graph */ public int getNumberOfNodes(); /** * Return the number of free nodes, which are nodes that are not a part of a * graph component */ public int getNumberOfFreeNodes(); /** * Get all free nodes. A free node is not in any component. */ public Iterator<T> getFreeNodes(); /** * Get all nodes that is connected to the given node. The constructible * pairs Pair<T> can then be used to obtain the edge type U using * getEdge(Pair<T>) */ public Iterator<T> getConnectedNodes(T node); /** * Get all edges connected to the given node * * @param node * @return An iterator over all edges connected to the given node */ public Iterator<U> getConnectedEdges(T node); } /** * Copyright (c) 2008-2010 Morten Silcowitz. * * This file is part of the Jinngine physics library * * Jinngine is published under the GPL license, available at * http://www.gnu.org/copyleft/gpl.html. */ // package jinngine.util; /** * Small pair class, for the purpose of indexing unordered pairs in a hash table */ final class Pair<T> { private final T o1; private final T o2; public Pair(T o1, T o2) { this.o1 = o1; this.o2 = o2; } // since this is an unordered pair, we use // the same hash code for interchanced objects @Override public final int hashCode() { return o1.hashCode() * o2.hashCode(); } @SuppressWarnings("unchecked") @Override public final boolean equals(Object other) { if (this == other) return true; if (other == null) return false; if (other instanceof Pair) { final Pair<T> otherpair = (Pair<T>) other; return ((o1.equals(otherpair.o1) && o2.equals(otherpair.o2)) || (o1.equals(otherpair.o2) && o2.equals(otherpair.o1))); } else { return false; } } public final T getFirst() { return o1; } public final T getSecond() { return o2; } public final boolean contains(T o) { return (o == o1 || o == o2); } }