sort orderby ordenar lista c# algorithm sorting dependencies topological-sort

orderby - ordenar lista c# order by



Cómo ordenar objetos dependientes por dependencia (10)

Tengo una colección:

List<VPair<Item, List<Item>> dependencyHierarchy;

El primer elemento en el par es algún objeto (elemento) y el segundo es una colección del mismo tipo de objetos de los que depende el primero. Quiero obtener una List<Item> en orden de dependencia, por lo que no hay elementos que dependan del primer elemento y así sucesivamente (¡no hay dependencia de ciclo!).

Entrada:

Item4 depends on Item3 and Item5 Item3 depends on Item1 Item1 does not depend on any one Item2 depends on Item4 Item5 does not depend on any one

Resultado:

Item1 Item5 Item3 Item4 Item2

Gracias.

SOLUCIÓN:

Clasificación topológica (gracias a Loïc Février por la idea)

y

ejemplo en C # , ejemplo en Java (gracias a xcud por excelentes ejemplos)


Combiné la idea de DMM con el algoritmo de búsqueda de profundidad en Wikipedia. Funciona perfecto para lo que necesitaba.

public static class TopologicalSorter { public static List<string> LastCyclicOrder = new List<string>(); //used to see what caused the cycle sealed class ItemTag { public enum SortTag { NotMarked, TempMarked, Marked } public string Item { get; set; } public SortTag Tag { get; set; } public ItemTag(string item) { Item = item; Tag = SortTag.NotMarked; } } public static IEnumerable<string> TSort(this IEnumerable<string> source, Func<string, IEnumerable<string>> dependencies) { TopologicalSorter.LastCyclicOrder.Clear(); List<ItemTag> allNodes = new List<ItemTag>(); HashSet<string> sorted = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string item in source) { if (!allNodes.Where(n => string.Equals(n.Item, item, StringComparison.OrdinalIgnoreCase)).Any()) { allNodes.Add(new ItemTag(item)); //don''t insert duplicates } foreach (string dep in dependencies(item)) { if (allNodes.Where(n => string.Equals(n.Item, dep, StringComparison.OrdinalIgnoreCase)).Any()) continue; //don''t insert duplicates allNodes.Add(new ItemTag(dep)); } } foreach (ItemTag tag in allNodes) { Visit(tag, allNodes, dependencies, sorted); } return sorted; } static void Visit(ItemTag tag, List<ItemTag> allNodes, Func<string, IEnumerable<string>> dependencies, HashSet<string> sorted) { if (tag.Tag == ItemTag.SortTag.TempMarked) { throw new GraphIsCyclicException(); } else if (tag.Tag == ItemTag.SortTag.NotMarked) { tag.Tag = ItemTag.SortTag.TempMarked; LastCyclicOrder.Add(tag.Item); foreach (ItemTag dep in dependencies(tag.Item).Select(s => allNodes.Where(t => string.Equals(s, t.Item, StringComparison.OrdinalIgnoreCase)).First())) //get item tag which falls with used string Visit(dep, allNodes, dependencies, sorted); LastCyclicOrder.Remove(tag.Item); tag.Tag = ItemTag.SortTag.Marked; sorted.Add(tag.Item); } } }


Después de haber luchado con esto por un tiempo, aquí está mi intento de un método de extensión TSort estilo Linq:

public static IEnumerable<T> TSort<T>( this IEnumerable<T> source, Func<T, IEnumerable<T>> dependencies, bool throwOnCycle = false ) { var sorted = new List<T>(); var visited = new HashSet<T>(); foreach( var item in source ) Visit( item, visited, sorted, dependencies, throwOnCycle ); return sorted; } private static void Visit<T>( T item, HashSet<T> visited, List<T> sorted, Func<T, IEnumerable<T>> dependencies, bool throwOnCycle ) { if( !visited.Contains( item ) ) { visited.Add( item ); foreach( var dep in dependencies( item ) ) Visit( dep, visited, sorted, dependencies, throwOnCycle ); sorted.Add( item ); } else { if( throwOnCycle && !sorted.Contains( item ) ) throw new Exception( "Cyclic dependency found" ); } }



Esta es mi propia reimplementación de la clasificación topológica, la idea se basa en http://tawani.blogspot.com/2009/02/topological-sorting-and-cyclic.html (El código fuente portado de Java consume demasiada memoria, verificar 50k objetos cuesta 50k * 50k * 4 = 10GB, lo cual es inaceptable. Además, todavía tiene la convención de codificación Java en algunos lugares)

using System.Collections.Generic; using System.Diagnostics; namespace Modules { /// <summary> /// Provides fast-algorithm and low-memory usage to sort objects based on their dependencies. /// </summary> /// <remarks> /// Definition: http://en.wikipedia.org/wiki/Topological_sorting /// Source code credited to: http://tawani.blogspot.com/2009/02/topological-sorting-and-cyclic.html /// Original Java source code: http://www.java2s.com/Code/Java/Collections-Data-Structure/Topologicalsorting.htm /// </remarks> /// <author>ThangTran</author> /// <history> /// 2012.03.21 - ThangTran: rewritten based on <see cref="TopologicalSorter"/>. /// </history> public class DependencySorter<T> { //************************************************** // // Private members // //************************************************** #region Private members /// <summary> /// Gets the dependency matrix used by this instance. /// </summary> private readonly Dictionary<T, Dictionary<T, object>> _matrix = new Dictionary<T, Dictionary<T, object>>(); #endregion //************************************************** // // Public methods // //************************************************** #region Public methods /// <summary> /// Adds a list of objects that will be sorted. /// </summary> public void AddObjects(params T[] objects) { // --- Begin parameters checking code ----------------------------- Debug.Assert(objects != null); Debug.Assert(objects.Length > 0); // --- End parameters checking code ------------------------------- // add to matrix foreach (T obj in objects) { // add to dictionary _matrix.Add(obj, new Dictionary<T, object>()); } } /// <summary> /// Sets dependencies of given object. /// This means <paramref name="obj"/> depends on these <paramref name="dependsOnObjects"/> to run. /// Please make sure objects given in the <paramref name="obj"/> and <paramref name="dependsOnObjects"/> are added first. /// </summary> public void SetDependencies(T obj, params T[] dependsOnObjects) { // --- Begin parameters checking code ----------------------------- Debug.Assert(dependsOnObjects != null); // --- End parameters checking code ------------------------------- // set dependencies Dictionary<T, object> dependencies = _matrix[obj]; dependencies.Clear(); // for each depended objects, add to dependencies foreach (T dependsOnObject in dependsOnObjects) { dependencies.Add(dependsOnObject, null); } } /// <summary> /// Sorts objects based on this dependencies. /// Note: because of the nature of algorithm and memory usage efficiency, this method can be used only one time. /// </summary> public T[] Sort() { // prepare result List<T> result = new List<T>(_matrix.Count); // while there are still object to get while (_matrix.Count > 0) { // get an independent object T independentObject; if (!this.GetIndependentObject(out independentObject)) { // circular dependency found throw new CircularReferenceException(); } // add to result result.Add(independentObject); // delete processed object this.DeleteObject(independentObject); } // return result return result.ToArray(); } #endregion //************************************************** // // Private methods // //************************************************** #region Private methods /// <summary> /// Returns independent object or returns NULL if no independent object is found. /// </summary> private bool GetIndependentObject(out T result) { // for each object foreach (KeyValuePair<T, Dictionary<T, object>> pair in _matrix) { // if the object contains any dependency if (pair.Value.Count > 0) { // has dependency, skip it continue; } // found result = pair.Key; return true; } // not found result = default(T); return false; } /// <summary> /// Deletes given object from the matrix. /// </summary> private void DeleteObject(T obj) { // delete object from matrix _matrix.Remove(obj); // for each object, remove the dependency reference foreach (KeyValuePair<T, Dictionary<T, object>> pair in _matrix) { // if current object depends on deleting object pair.Value.Remove(obj); } } #endregion } /// <summary> /// Represents a circular reference exception when sorting dependency objects. /// </summary> public class CircularReferenceException : Exception { /// <summary> /// Initializes a new instance of the <see cref="CircularReferenceException"/> class. /// </summary> public CircularReferenceException() : base("Circular reference found.") { } } }


Este es un código refactorizado de la publicación https://.com/a/9991916/4805491 .

// Version 1 public static class TopologicalSorter<T> where T : class { public struct Item { public readonly T Object; public readonly T Dependency; public Item(T @object, T dependency) { Object = @object; Dependency = dependency; } } public static T[] Sort(T[] objects, Func<T, T, bool> isDependency) { return Sort( objects.ToList(), isDependency ).ToArray(); } public static T[] Sort(T[] objects, Item[] dependencies) { return Sort( objects.ToList(), dependencies.ToList() ).ToArray(); } private static List<T> Sort(List<T> objects, Func<T, T, bool> isDependency) { return Sort( objects, GetDependencies( objects, isDependency ) ); } private static List<T> Sort(List<T> objects, List<Item> dependencies) { var result = new List<T>( objects.Count ); while (objects.Any()) { var obj = GetIndependentObject( objects, dependencies ); RemoveObject( obj, objects, dependencies ); result.Add( obj ); } return result; } private static List<Item> GetDependencies(List<T> objects, Func<T, T, bool> isDependency) { var dependencies = new List<Item>(); for (var i = 0; i < objects.Count; i++) { var obj1 = objects[i]; for (var j = i + 1; j < objects.Count; j++) { var obj2 = objects[j]; if (isDependency( obj1, obj2 )) dependencies.Add( new Item( obj1, obj2 ) ); // obj2 is dependency of obj1 if (isDependency( obj2, obj1 )) dependencies.Add( new Item( obj2, obj1 ) ); // obj1 is dependency of obj2 } } return dependencies; } private static T GetIndependentObject(List<T> objects, List<Item> dependencies) { foreach (var item in objects) { if (!GetDependencies( item, dependencies ).Any()) return item; } throw new Exception( "Circular reference found" ); } private static IEnumerable<Item> GetDependencies(T obj, List<Item> dependencies) { return dependencies.Where( i => i.Object == obj ); } private static void RemoveObject(T obj, List<T> objects, List<Item> dependencies) { objects.Remove( obj ); dependencies.RemoveAll( i => i.Object == obj || i.Dependency == obj ); } } // Version 2 public class TopologicalSorter { public static T[] Sort<T>(T[] source, Func<T, T, bool> isDependency) { var list = new LinkedList<T>( source ); var result = new List<T>(); while (list.Any()) { var obj = GetIndependentObject( list, isDependency ); list.Remove( obj ); result.Add( obj ); } return result.ToArray(); } private static T GetIndependentObject<T>(IEnumerable<T> list, Func<T, T, bool> isDependency) { return list.First( i => !GetDependencies( i, list, isDependency ).Any() ); } private static IEnumerable<T> GetDependencies<T>(T obj, IEnumerable<T> list, Func<T, T, bool> isDependency) { return list.Where( i => isDependency( obj, i ) ); // i is dependency of obj } }


Me facilitaría esto almacenando las dependencias de un artículo dentro del artículo mismo:

public class Item { private List<Item> m_Dependencies = new List<Item>(); protected AddDependency(Item _item) { m_Dependencies.Add(_item); } public Item() { }; // eo ctor public List<Item> Dependencies {get{return(m_Dependencies);};} } // eo class Item

Luego, dado esto, puede implementar un delegado de clasificación personalizado para la lista que se ordena en función de si el elemento dado se encuentra dentro de la lista de dependencias del otro:

int CompareItem(Item _1, Item _2) { if(_2.Dependencies.Contains(_1)) return(-1); else if(_1.Dependencies.Contains(_2)) return(1); else return(0); }


Me gustó la respuesta de DMM, pero asume que los nodos de entrada son hojas (que pueden o no ser lo que se espera).

Estoy publicando una solución alternativa usando LINQ que no hace esta suposición. Además, esta solución utiliza el yield return para poder devolver rápidamente las hojas (usando, por ejemplo, TakeWhile ).

public static IEnumerable<T> TopologicalSort<T>(this IEnumerable<T> nodes, Func<T, IEnumerable<T>> connected) { var elems = nodes.ToDictionary(node => node, node => new HashSet<T>(connected(node))); while (elems.Count > 0) { var elem = elems.FirstOrDefault(x => x.Value.Count == 0); if (elem.Key == null) { throw new ArgumentException("Cyclic connections are not allowed"); } elems.Remove(elem.Key); foreach (var selem in elems) { selem.Value.Remove(elem.Key); } yield return elem.Key; } }


No me gustan los métodos recursivos, por lo que DMM está fuera. Krumelur se ve bien, pero parece usar mucha memoria? Hizo un método alternativo basado en la pila que parece funcionar. Necesita más pruebas, los comentarios son bienvenidos. TODO: sin protección de ciclo. Lanzará OutOfMemoryException en ciclo :-(

public static IEnumerable<T> TopogicalSequence<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> deps) { var visited = new HashSet<T>(); Stack<T> stack = new Stack<T>(); foreach (T t in source) { if (visited.Contains(t)) continue; stack.Clear(); stack.Push(t); while (stack.Count > 0) { var ele = stack.Peek(); bool depPushed = false; foreach (var dep in deps(ele).Where(d => !visited.Contains(d))) { stack.Push(dep); depPushed = true; } if (!depPushed) { ele = stack.Pop(); if (visited.Add(ele)) yield return ele; } } } }


Una idea diferente, para casos con solo un "padre" dependiendo:

En lugar de déps, almacenarías a los padres.
Entonces puede decir muy fácilmente si un problema es una dependencia de algún otro.
Y luego use Comparable<T> , que reclamaría las dependencias "lesser" y la dependencia "greater".
Y luego simplemente llame a Collections.sort( List<T>, ParentComparator<T>);

Para el escenario de múltiples padres, se necesitaría una búsqueda en árbol que daría como resultado una ejecución lenta. Pero eso podría resolverse con un caché en forma de matriz A *.


Hay un nuget para eso .

Para aquellos de nosotros que preferimos no reinventar la rueda: use nuget para instalar la biblioteca QuickGraph .NET, que incluye múltiples algoritmos de gráficos, incluido el topológico .

Para usarlo, debe crear una instancia de AdjacencyGraph<,> como AdjacencyGraph<String, SEdge<String>> . Luego, si incluye las extensiones apropiadas:

using QuickGraph.Algorithms;

Puedes llamar:

var sorted = myGraph.TopologicalSort();

Para obtener una lista de nodos ordenados.