Topological sort utility

This commit is contained in:
Greg Wilkins 2015-06-24 16:14:31 +10:00
parent c792677db4
commit 32c71261e1
2 changed files with 295 additions and 0 deletions

View File

@ -0,0 +1,110 @@
package org.eclipse.jetty.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Topological Sort a list or array.
* <p>The algorithm has the additional characteristic that dependency sets are
* sorted by the original list order so that order is preserved when possible
* @param <T> The type to be sorted.
*/
public class TopologicalSort<T>
{
private final Map<T,Set<T>> _dependencies = new HashMap<>();
public void addDependency(T dependent, T dependency)
{
Set<T> set = _dependencies.get(dependent);
if (set==null)
{
set=new HashSet<>();
_dependencies.put(dependent,set);
}
set.add(dependency);
}
public void sort(T[] list)
{
List<T> sorted = new ArrayList<>();
Set<T> visited = new HashSet<>();
Comparator<T> comparator = new InitialOrderComparitor<>(list);
for (T t : list)
visit(t,visited,sorted,comparator);
sorted.toArray(list);
}
public void sort(Collection<T> list)
{
List<T> sorted = new ArrayList<>();
Set<T> visited = new HashSet<>();
Comparator<T> comparator = new InitialOrderComparitor<>(list);
for (T t : list)
visit(t,visited,sorted,comparator);
list.clear();
list.addAll(sorted);
}
private void visit(T t, Set<T> visited, List<T> sorted,Comparator<T> comparator)
{
if( !visited.contains(t) )
{
visited.add( t );
Set<T> dependencies = _dependencies.get(t);
if (dependencies!=null)
{
SortedSet<T> ordered_deps = new TreeSet<>(comparator);
ordered_deps.addAll(dependencies);
for (T d:ordered_deps)
visit(d,visited,sorted,comparator);
}
sorted.add(t);
}
else if (!sorted.contains(t))
throw new IllegalStateException("cyclic");
}
private static class InitialOrderComparitor<T> implements Comparator<T>
{
private final Map<T,Integer> _indexes = new HashMap<>();
InitialOrderComparitor(T[] initial)
{
int i=0;
for (T t : initial)
_indexes.put(t,i++);
}
InitialOrderComparitor(Collection<T> initial)
{
int i=0;
for (T t : initial)
_indexes.put(t,i++);
}
@Override
public int compare(T o1, T o2)
{
Integer i1=_indexes.get(o1);
Integer i2=_indexes.get(o2);
if (i1==null || i2==null || i1.equals(o2))
return 0;
if (i1<i2)
return -1;
return 1;
}
}
}

View File

@ -0,0 +1,185 @@
package org.eclipse.jetty.util;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
public class TopologicalSortTest
{
@Test
public void testNoDependencies()
{
String[] s = { "D","E","C","B","A" };
TopologicalSort<String> ts = new TopologicalSort<>();
ts.sort(s);
Assert.assertThat(s,Matchers.arrayContaining("D","E","C","B","A"));
}
@Test
public void testSimpleLinear()
{
String[] s = { "D","E","C","B","A" };
TopologicalSort<String> ts = new TopologicalSort<>();
ts.addDependency("B","A");
ts.addDependency("C","B");
ts.addDependency("D","C");
ts.addDependency("E","D");
ts.sort(s);
Assert.assertThat(s,Matchers.arrayContaining("A","B","C","D","E"));
}
@Test
public void testDisjoint()
{
String[] s = { "A","C","B","CC","AA","BB"};
TopologicalSort<String> ts = new TopologicalSort<>();
ts.addDependency("B","A");
ts.addDependency("C","B");
ts.addDependency("BB","AA");
ts.addDependency("CC","BB");
ts.sort(s);
Assert.assertThat(s,Matchers.arrayContaining("A","B","C","AA","BB","CC"));
}
@Test
public void testDisjointReversed()
{
String[] s = { "CC","AA","BB","A","C","B"};
TopologicalSort<String> ts = new TopologicalSort<>();
ts.addDependency("B","A");
ts.addDependency("C","B");
ts.addDependency("BB","AA");
ts.addDependency("CC","BB");
ts.sort(s);
Assert.assertThat(s,Matchers.arrayContaining("AA","BB","CC","A","B","C"));
}
@Test
public void testDisjointMixed()
{
String[] s = { "CC","A","AA","C","BB","B"};
TopologicalSort<String> ts = new TopologicalSort<>();
ts.addDependency("B","A");
ts.addDependency("C","B");
ts.addDependency("BB","AA");
ts.addDependency("CC","BB");
ts.sort(s);
// Check direct ordering
Assert.assertThat(indexOf(s,"A"),lessThan(indexOf(s,"B")));
Assert.assertThat(indexOf(s,"B"),lessThan(indexOf(s,"C")));
Assert.assertThat(indexOf(s,"AA"),lessThan(indexOf(s,"BB")));
Assert.assertThat(indexOf(s,"BB"),lessThan(indexOf(s,"CC")));
}
@Test
public void testTree()
{
String[] s = { "LeafA0","LeafB0","LeafA1","Root","BranchA","LeafB1","BranchB"};
TopologicalSort<String> ts = new TopologicalSort<>();
ts.addDependency("BranchB","Root");
ts.addDependency("BranchA","Root");
ts.addDependency("LeafA1","BranchA");
ts.addDependency("LeafA0","BranchA");
ts.addDependency("LeafB0","BranchB");
ts.addDependency("LeafB1","BranchB");
ts.sort(s);
// Check direct ordering
Assert.assertThat(indexOf(s,"Root"),lessThan(indexOf(s,"BranchA")));
Assert.assertThat(indexOf(s,"Root"),lessThan(indexOf(s,"BranchB")));
Assert.assertThat(indexOf(s,"BranchA"),lessThan(indexOf(s,"LeafA0")));
Assert.assertThat(indexOf(s,"BranchA"),lessThan(indexOf(s,"LeafA1")));
Assert.assertThat(indexOf(s,"BranchB"),lessThan(indexOf(s,"LeafB0")));
Assert.assertThat(indexOf(s,"BranchB"),lessThan(indexOf(s,"LeafB1")));
// check remnant ordering of original list
Assert.assertThat(indexOf(s,"BranchA"),lessThan(indexOf(s,"BranchB")));
Assert.assertThat(indexOf(s,"LeafA0"),lessThan(indexOf(s,"LeafA1")));
Assert.assertThat(indexOf(s,"LeafB0"),lessThan(indexOf(s,"LeafB1")));
}
@Test
public void testPreserveOrder()
{
String[] s = { "Deep","Foobar","Wibble","Bozo","XXX","12345","Test"};
TopologicalSort<String> ts = new TopologicalSort<>();
ts.addDependency("Deep","Test");
ts.addDependency("Deep","Wibble");
ts.addDependency("Deep","12345");
ts.addDependency("Deep","XXX");
ts.addDependency("Deep","Foobar");
ts.addDependency("Deep","Bozo");
ts.sort(s);
Assert.assertThat(s,Matchers.arrayContaining("Foobar","Wibble","Bozo","XXX","12345","Test","Deep"));
}
@Test
public void testSimpleLoop()
{
String[] s = { "A","B","C","D","E" };
TopologicalSort<String> ts = new TopologicalSort<>();
ts.addDependency("B","A");
ts.addDependency("A","B");
try
{
ts.sort(s);
Assert.fail();
}
catch(IllegalStateException e)
{
Assert.assertThat(e.getMessage(),Matchers.containsString("cyclic"));
}
}
@Test
public void testDeepLoop()
{
String[] s = { "A","B","C","D","E" };
TopologicalSort<String> ts = new TopologicalSort<>();
ts.addDependency("B","A");
ts.addDependency("C","B");
ts.addDependency("D","C");
ts.addDependency("E","D");
ts.addDependency("A","E");
try
{
ts.sort(s);
Assert.fail();
}
catch(IllegalStateException e)
{
Assert.assertThat(e.getMessage(),Matchers.containsString("cyclic"));
}
}
private int indexOf(String[] list,String s)
{
for (int i=0;i<list.length;i++)
if (list[i]==s)
return i;
return -1;
}
}