Skip to content

Commit

Permalink
topo sort
Browse files Browse the repository at this point in the history
  • Loading branch information
rjnichols committed Oct 28, 2011
1 parent 661e318 commit d3bc90e
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2010 Visural.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package com.visural.common.collection;

public interface DirectionProvider<T> {

Directional<T> For(T node);
}
26 changes: 26 additions & 0 deletions visural-common/src/com/visural/common/collection/Directional.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2010 Visural.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package com.visural.common.collection;

import java.util.Collection;

public interface Directional<T> {

Collection<T> follows();

Collection<T> precedes();
}
125 changes: 125 additions & 0 deletions visural-common/src/com/visural/common/collection/TopologicalSort.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2010 Visural.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package com.visural.common.collection;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Allows determination of evaluation order of a DAG
* @author Visural
* @param <T>
*/
public class TopologicalSort<T> {

private Set<T> orphaned = Sets.newHashSet();
private Set<T> noFollows = Sets.newHashSet();
// the values that precede the keys
private Multimap<T, T> precedes = HashMultimap.create();

public TopologicalSort(T... directedNodes) {
this(new NodeInternalDirectionProvider(), directedNodes);
}

public TopologicalSort(Collection<T> directedNodes) {
this(new NodeInternalDirectionProvider(), directedNodes);
}

public TopologicalSort(DirectionProvider<T> directionProvider, T... directedNodes) {
this(directionProvider, Arrays.asList(directedNodes));
}

public TopologicalSort(DirectionProvider<T> directionProvider, Collection<T> directedNodes) {
for (T node : directedNodes) {
Directional nodeDir = directionProvider.For(node);
Collection<T> fol = nodeDir.follows();
for (T follower : fol) {
precedes.put(node, follower);
}
Collection<T> pres = nodeDir.precedes();
for (T pre : pres) {
precedes.put(pre, node);
}
orphaned.add(node);
noFollows.add(node);
}
// remove node from noEdges which have edges
noFollows.removeAll(precedes.values());
orphaned.removeAll(precedes.keySet());
orphaned.removeAll(precedes.values());
}

public boolean isCyclic() {
try {
evaluationOrder();
} catch (IllegalArgumentException e) {
return true;
}
return false;
}

private List<T> evalCache = null;

/**
* Determine the evaluation order of a DAG. An IllegalArgumentException is
* thrown if the graph is cyclic.
* @return
*/
public List<T> evaluationOrder() {
if (evalCache != null) return evalCache;
List<T> eval = new ArrayList<T>(orphaned);

if (noFollows.isEmpty() && !precedes.isEmpty()) {
throw new IllegalArgumentException("Cycle found in dependency graph");
} else {
Set<T> visited = new HashSet<T>();
for (T node : noFollows) {
visit(eval, node, visited);
}
}
evalCache = eval;
return eval;
}

private void visit(List<T> eval, T node, Set<T> stackVisited) {
if (stackVisited.contains(node)) {
throw new IllegalArgumentException("Cycle found in dependency graph");
} else {
stackVisited.add(node);
}
if (!eval.contains(node)) {
for (T parent : precedes.get(node)) {
visit(eval, parent, stackVisited);
}
eval.add(node);
}
stackVisited.remove(node);
}

private static class NodeInternalDirectionProvider<T extends Directional<T>> implements DirectionProvider<T> {
public Directional<T> For(T node) {
return node;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2011 Visural.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package com.visural.common.collection;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import junit.framework.TestCase;

public class TopologicalSortTest extends TestCase {

public void testSort() {
Node a = new Node("a");
Node b = new Node("b");
Node c = new Node("c");
Node d = new Node("d");
Node e = new Node("e");
Node o = new Node("o");
d.setFollows(c,b);
a.setPrecedes(b);
d.setPrecedes(e);
e.setFollows(b);
TopologicalSort<Node> ts = new TopologicalSort(a, b, c, d, e, o);
String order = ts.evaluationOrder().toString();
assertTrue(order.equals("[o, c, a, b, d, e]") || order.equals("[o, a, c, b, d, e]"));
}

public void testCycle() {
Node a = new Node("a");
Node b = new Node("b");
Node c = new Node("c");
Node d = new Node("d");
Node e = new Node("e");
Node o = new Node("o");
d.setFollows(c,b);
a.setPrecedes(b);
d.setPrecedes(e, a);
e.setFollows(b);
TopologicalSort<Node> ts = new TopologicalSort(a, b, c, d, e, o);
assertTrue(ts.isCyclic());
}

public static class Node implements Directional<Node> {
private final String name;

public Node(String name) {
this.name = name;
}

public String getName() {
return name;
}

private Set<Node> precedes = new HashSet();
private Set<Node> follows = new HashSet();

public void setFollows(Node... afters) {
follows.clear();
follows.addAll(Arrays.asList(afters));
}

public void setPrecedes(Node... befores) {
precedes.clear();
precedes.addAll(Arrays.asList(befores));
}

public Collection<Node> follows() {
return follows;
}

public Collection<Node> precedes() {
return precedes;
}

@Override
public String toString() {
return name;
}

}
}

0 comments on commit d3bc90e

Please sign in to comment.