Topological sort utility
This commit is contained in:
parent
c792677db4
commit
32c71261e1
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue