using System; using System.Collections.Generic; using System.Linq; namespace Umbraco.Core { /// /// Topological Sort algorithm for sorting items based on dependencies. /// Use the static method TopologicalSorter.GetSortedItems for a convenient /// way of sorting a list of items with dependencies between them. /// public class TopologicalSorter { private readonly int[] _vertices; // list of vertices private readonly int[,] _matrix; // adjacency matrix private int _numVerts; // current number of vertices private readonly int[] _sortedArray; public TopologicalSorter(int size) { _vertices = new int[size]; _matrix = new int[size, size]; _numVerts = 0; for (int i = 0; i < size; i++) for (int j = 0; j < size; j++) _matrix[i, j] = 0; _sortedArray = new int[size]; // sorted vert labels } #region Public Methods public int AddVertex(int vertex) { _vertices[_numVerts++] = vertex; return _numVerts - 1; } public void AddEdge(int start, int end) { _matrix[start, end] = 1; } public int[] Sort() // toplogical sort { while (_numVerts > 0) // while vertices remain, { // get a vertex with no successors, or -1 int currentVertex = NoSuccessors(); if (currentVertex == -1) // must be a cycle throw new Exception("Graph has cycles"); // insert vertex label in sorted array (start at end) _sortedArray[_numVerts - 1] = _vertices[currentVertex]; DeleteVertex(currentVertex); // delete vertex } // vertices all gone; return sortedArray return _sortedArray; } #endregion #region Private Helper Methods // returns vert with no successors (or -1 if no such verts) private int NoSuccessors() { for (int row = 0; row < _numVerts; row++) { bool isEdge = false; // edge from row to column in adjMat for (int col = 0; col < _numVerts; col++) { if (_matrix[row, col] > 0) // if edge to another, { isEdge = true; break; // this vertex has a successor try another } } if (!isEdge) // if no edges, has no successors return row; } return -1; // no } private void DeleteVertex(int delVert) { // if not last vertex, delete from vertexList if (delVert != _numVerts - 1) { for (int j = delVert; j < _numVerts - 1; j++) _vertices[j] = _vertices[j + 1]; for (int row = delVert; row < _numVerts - 1; row++) MoveRowUp(row, _numVerts); for (int col = delVert; col < _numVerts - 1; col++) MoveColLeft(col, _numVerts - 1); } _numVerts--; // one less vertex } private void MoveRowUp(int row, int length) { for (int col = 0; col < length; col++) _matrix[row, col] = _matrix[row + 1, col]; } private void MoveColLeft(int col, int length) { for (int row = 0; row < length; row++) _matrix[row, col] = _matrix[row, col + 1]; } #endregion #region Static methods public static IEnumerable GetSortedItems(List> fields) where T : class { int[] sortOrder = GetTopologicalSortOrder(fields); var list = new List(); for (int i = 0; i < sortOrder.Length; i++) { var field = fields[sortOrder[i]]; list.Add(field.Item.Value); } list.Reverse(); return list; } internal static int[] GetTopologicalSortOrder(List> fields) where T : class { var g = new TopologicalSorter(fields.Count()); var indexes = new Dictionary(); //add vertices for (int i = 0; i < fields.Count(); i++) { indexes[fields[i].Alias.ToLowerInvariant()] = g.AddVertex(i); } //add edges for (int i = 0; i < fields.Count; i++) { if (fields[i].DependsOn != null) { for (int j = 0; j < fields[i].DependsOn.Length; j++) { if (indexes.ContainsKey(fields[i].DependsOn[j].ToLowerInvariant()) == false) throw new IndexOutOfRangeException( string.Format( "The alias '{0}' has an invalid dependency. The dependency '{1}' does not exist in the list of aliases", fields[i], fields[i].DependsOn[j])); g.AddEdge(i, indexes[fields[i].DependsOn[j].ToLowerInvariant()]); } } } int[] result = g.Sort(); return result; } #endregion public class DependencyField where T : class { public DependencyField() { } public DependencyField(string @alias, string[] dependsOn, Lazy item) { Alias = alias; DependsOn = dependsOn; Item = item; } public string Alias { get; set; } public string[] DependsOn { get; set; } public Lazy Item { get; set; } } } }