aboutsummaryrefslogblamecommitdiffstats
path: root/src/algorithms/sorting/other/TopologicalSort.java
blob: 887fa99bcf15638a5a038cccb7dc597ef0c20d4b (plain) (tree)
1
2
   
                                                     
















































































































































































































































                                                                                                                             
/**
 * Copyright © 2014  Mattias Andrée (m@maandree.se)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package algorithms.sorting.other;

import java.util.*;


/**
 * Topological sorting class. There are a few ways to perform
 * a topological sort, this class implements an topological
 * sort with advisory dependencies and strict dependencies.
 * Dependencies are broken when there are cyclic dependencies,
 * but if the dependency is strict rather than advisory, the
 * algorithm fails. Additionally, this this, implementation
 * uses two input parameters, a map from items to sets of
 * dependencies, and a map from items, to a set of strict
 * dependencies. The strict dependencies must appear in both
 * of the sets, whilst the advisory dependencies must only
 * appear in the first set of dependencies. This implemention
 * tries to minimise the number of transversially broken
 * dependencies. It is advisable to use both hash based maps
 * and sets.
 * 
 * @param  <T>  The data type that has been sorted
 */
public class TopologicalSort<T>
{
    /**
     * Constructor
     */
    private TopologicalSort()
    {
	/* Do nothing */
    }
    
    
    
    /**
     * List of missing dependencies. There may be duplicates,
     * see {@link #missingDependent}.
     */
    public ArrayList<T> missingDependency = new ArrayList<T>();
    
    /**
     * List of items with missing dependencies. For any give index
     * i, the element in {@link #missingDependency} with index i
     * is required by the element with index i in this list.
     */
    public ArrayList<T> missingDependent = new ArrayList<T>();
    
    /**
     * The items in topological order
     */
    public ArrayList<T> order = new ArrayList<T>();
    
    /**
     * If an items exists as a key in this map it has been used
     * to break a cyclic dependency. The associated value of a
     * key is a set of all direct dependencies that will appear
     * later in the topologically ordered list {@link #order}.
     */
    public HashMap<T, HashSet<T>> breaks = new HashMap<T, HashSet<T>>();
    
    /**
     * Whether the topological sort was successful, i.e., not
     * absolute dependencies needed to be broken. If this value
     * is {@code false}, the topologically ordered list and
     * the map of cyclic dependency breaks may no be complete.
     */
    public boolean successful = false;
    
    
    
    /**
     * Topolocally sort data.
     * 
     * @param   dependencies  Map for items to their advisory dependencies, will be modified
     * @param   absolutes     Map for items to their absolute dependencies
     * @return                A data structure with the result of the topological sort
     */
    @SuppressWarnings("unchecked")
    public static <T> TopologicalSort<T> tsort(Map<T, Set<T>> dependencies, Map<T, Set<T>> absolutes)
    {
	TopologicalSort<T> rc = new TopologicalSort<T>();
	if (absolutes == null) /* sugar */
	    absolutes = new HashMap<T, Set<T>>();
	
	/* Find, list and remove missing dependencies */
	{
	    HashMap<T, ArrayList<T>> removed = new HashMap<T, ArrayList<T>>();
	    for (Map.Entry<T, Set<T>> req_deps : dependencies.entrySet())
	    {
		T req = req_deps.getKey();
		for (T dep : req_deps.getValue())
		    if (dependencies.containsKey(dep) == false)
		    {
			rc.missingDependency.add(dep);
			rc.missingDependent.add(req);
			if (removed.containsKey(dep) == false)
			    removed.put(dep, new ArrayList<T>());
			removed.get(dep).add(req);
		    }
	    }
	    for (Map.Entry<T, ArrayList<T>> remove_reqs : removed.entrySet())
	    {
		T remove = remove_reqs.getKey();
		for (T req : remove_reqs.getValue())
		    dependencies.get(req).remove(remove);
	    }
	}
	
	/* Sort cyclic graph topologically */
	{
	    ArrayList<T> removed = new ArrayList<T>();
	    removed.add(null);
	    while (removed.size() > 0)
	    {
		/* Sort acyclic graph topologically */
		while (removed.size() > 0)
		{
		    removed.clear();
		    /* Report and remove items with no unresolved dependency */
		    T[] dependencies_ = (T[])(new Object[dependencies.size()]); /* needed to avoid concurrent modification */
		    int i = 0;
		    for (T key : dependencies.keySet())
			dependencies_[i++] = key;
		    for (T item : dependencies_)
			if (dependencies.get(item).size() == 0)
			{
			    rc.order.add(item);
			    removed.add(item);
			    dependencies.remove(item);
			}
		    /* Remove reported items from dependency lists */
		    for (Map.Entry<T, Set<T>> req_deps : dependencies.entrySet())
		    {
			T req = req_deps.getKey();
			Set<T> deps = req_deps.getValue();
			for (T old : removed)
			    if (deps.contains(old))
				deps.remove(old);
		    }
		}
		
		/* Break one cycle with as few transversial dependencies as possible */
		if (dependencies.size() > 0)
		{
		    T best = null;
		    HashSet<T> bestBreaks = null;
		    /* Find best cycle break */
		    for (Map.Entry<T, Set<T>> item_deps : dependencies.entrySet())
		    {
			T item = item_deps.getKey();
			Set<T> queue_ = item_deps.getValue();
			Set<T> abs_ = absolutes.get(item);
			
			HashSet<T> abs = new HashSet<T>();
			if (abs_ != null)
			    for (T elem : abs_)
				abs.add(elem);
			
			HashSet<T> deps = new HashSet<T>();
			T[] queue = (T[])(new Object[queue_.size()]);
			int queue_h = 0, queue_t = 0;
			for (T elem : queue_)
			    queue[queue_t++] = elem;
			deps.add(item);
			boolean bad = false;
			
			while (queue_h != queue_t)
			{
			    T dep = queue[queue_h++];
			    if (abs.contains(dep))
			    {
				bad = true;
				break;
			    }
			    if (deps.contains(dep))
				continue;
			    deps.add(dep);
			    
			    Set<T> extra = dependencies.get(dep);
			    queue_t -= queue_h;
			    System.arraycopy(queue, queue_h, queue = (T[])(new Object[queue_t + extra.size()]), 0, queue_t);
			    queue_h = 0;
			    for (T elem : extra)
				queue[queue_t++] = elem;
			    
			    if ((extra = absolutes.get(dep)) != null)
				for (T elem : extra)
				    abs.add(elem);
			}
			
			if (bad)
			    continue;
			deps.remove(item);
			if ((best == null) || (bestBreaks.size() > deps.size()))
			{
			    best = item;
			    bestBreaks = deps;
			}
		    }
		    
		    /* List cycle break and remove item */
		    if (best == null)
			return rc;
		    rc.order.add(best);
		    rc.breaks.put(best, bestBreaks);
		    removed.add(best);
		    dependencies.remove(best);
		    
		    /* Remove item from dependency lists */
		    for (Map.Entry<T, Set<T>> item_deps : dependencies.entrySet())
		    {
			T item = item_deps.getKey();
			Set<T> deps = item_deps.getValue();
			if (deps.contains(item) == false)
			    deps.remove(best);
		    }
		}
	    }
	}
	
	rc.successful = true;
	return rc;
    }
    
}