com.alee.utils.sort.TopologicalSorter Maven / Gradle / Ivy
/*
* This file is part of WebLookAndFeel library.
*
* WebLookAndFeel library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* WebLookAndFeel library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WebLookAndFeel library. If not, see .
*/
package com.alee.utils.sort;
import java.util.*;
/**
* Custom sorter that implements topological data sorting.
*
* @param data type
* @author Mikle Garin
*/
public final class TopologicalSorter
{
/**
* {@link TopologicalGraphProvider}.
*/
private final TopologicalGraphProvider provider;
/**
* Constructs new {@link TopologicalSorter}.
*
* @param provider {@link TopologicalGraphProvider}
*/
public TopologicalSorter ( final TopologicalGraphProvider provider )
{
super ();
this.provider = provider;
}
/**
* Performs topological data sort using {@link TopologicalGraphProvider} to build nodes graph and returns sorted data list.
*
* @return sorted data list
*/
public List list ()
{
// Building nodes structure
final List> nodes = new ArrayList> ();
final Map> nodesCache = new HashMap> ();
for ( final T root : provider.getRoots () )
{
buildNodeStructure ( root, nodes, nodesCache );
}
// Performing sorting
final List> sortedNodes = doTopologicalSort ( nodes );
// Mapping data
final List sortedData = new ArrayList ( sortedNodes.size () );
for ( final Node node : sortedNodes )
{
sortedData.add ( node.data );
}
return sortedData;
}
/**
* Builds single node structure using provided data.
*
* @param data structure root node data
* @param nodes existing graph nodes
* @param nodesCache existing graph nodes cache
* @return root structure node
*/
private Node buildNodeStructure ( final T data, final List> nodes, final Map> nodesCache )
{
final Node node;
if ( nodesCache.containsKey ( data ) )
{
// Retrieving existing node
node = nodesCache.get ( data );
}
else
{
// Creating new node
node = new Node ( data );
nodes.add ( node );
nodesCache.put ( data, node );
// Creating node children
for ( final T child : provider.getChildren ( data ) )
{
node.addEdge ( buildNodeStructure ( child, nodes, nodesCache ) );
}
}
return node;
}
/**
* Performs topological data sort using provided graph nodes and returns sorted graph nodes list.
*
* @param nodes graph nodes to sort
* @return sorted sorted graph nodes list
*/
private List> doTopologicalSort ( final List> nodes )
{
// L <- Empty list that will contain the sorted elements
final ArrayList> L = new ArrayList> ();
// S <- Set of all nodes with no incoming edges
final HashSet> S = new HashSet> ();
for ( final Node n : nodes )
{
if ( n.inEdges.size () == 0 )
{
S.add ( n );
}
}
// while S is non-empty do
while ( !S.isEmpty () )
{
// remove a node n from S
final Node n = S.iterator ().next ();
S.remove ( n );
// insert n into L
L.add ( n );
// for each node m with an edge e from n to m do
for ( final Iterator> it = n.outEdges.iterator (); it.hasNext (); )
{
// remove edge e from the graph
final Edge e = it.next ();
final Node m = e.to;
// Remove edge from n
it.remove ();
// Remove edge from m
m.inEdges.remove ( e );
// if m has no other incoming edges then insert m into S
if ( m.inEdges.isEmpty () )
{
S.add ( m );
}
}
}
// Check to see if all edges are removed
boolean cycle = false;
for ( final Node n : nodes )
{
if ( !n.inEdges.isEmpty () )
{
cycle = true;
break;
}
}
if ( !cycle )
{
return L;
}
else
{
throw new RuntimeException ( "Cycle present, topological sort not possible" );
}
}
/**
* Single graph {@link Node} containing data.
*
* @param data type
*/
private static class Node
{
/**
* {@link Node} data.
*/
public final T data;
/**
* Incoming {@link Edge}s of this {@link Node}.
*/
public final HashSet> inEdges;
/**
* Outgoing {@link Edge}s of this {@link Node}.
*/
public final HashSet> outEdges;
/**
* Constructs new {@link Node}.
*
* @param data {@link Node} data
*/
public Node ( final T data )
{
this.data = data;
inEdges = new HashSet> ();
outEdges = new HashSet> ();
}
/**
* Adds new outgoing {@link Edge} from this {@link Node} to other {@link Node}.
*
* @param node other {@link Node}
* @return this {@link Node}
*/
public Node addEdge ( final Node node )
{
final Edge edge = new Edge ( this, node );
outEdges.add ( edge );
node.inEdges.add ( edge );
return this;
}
@Override
public String toString ()
{
return data != null ? data.toString () : null;
}
}
/**
* Single {@link Node} edge.
*
* @param data type
*/
private static class Edge
{
/**
* {@link Node} this {@link Edge} is outgoing for.
*/
public final Node from;
/**
* {@link Node} this {@link Edge} is incoming for.
*/
public final Node to;
/**
* Constructs new {@link Edge}.
*
* @param from {@link Node} this {@link Edge} is outgoing for
* @param to {@link Node} this {@link Edge} is incoming for
*/
public Edge ( final Node from, final Node to )
{
this.from = from;
this.to = to;
}
@Override
public boolean equals ( final Object object )
{
if ( object instanceof TopologicalSorter.Edge )
{
final Edge edge = ( Edge ) object;
return edge.from == from && edge.to == to;
}
return false;
}
}
}