86 votes

Comment trier les objets dépendants par dépendance

J'ai une collection :

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

Le premier élément de la paire est un objet (item) et le second est une collection d'objets du même type dont dépend le premier. Je veux obtenir un List<Item> par ordre de dépendance, de sorte qu'il n'y a pas d'éléments qui dépendent du premier élément et ainsi de suite (pas de dépendance cyclique !).

Entrée :

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 

Résultat :

Item1
Item5
Item3
Item4
Item2

Merci.

SOLUTION :

Tri topologique (merci à Loïc Février pour l'idée)

et

exemple sur C# , exemple sur Java (merci à xcud pour de bons exemples)

0 votes

Si quelqu'un tombe sur ce sujet et cherche un paquet nuget C#, en voici un que j'ai créé : github.com/madelson/MedallionTopologicalSort

97voto

DMM Points 601

Après avoir lutté avec cela pendant un certain temps, voici ma tentative d'une méthode d'extension TSort de style 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" );
    }
}

5 votes

+1 Beaucoup plus simple et semble fonctionner pour moi. Le seul changement que j'ai fait est d'utiliser un Dictionary<T, object> au lieu de List<T> para visited - il devrait être plus rapide pour les grandes collections.

3 votes

Merci E M - J'ai mis à jour pour utiliser un HashSet pour la collection visitée.

2 votes

+1 J'ai jeté un coup d'œil au pseudo-code de l'algorithme sur Wikipedia et j'ai pensé qu'il serait assez facile à mettre en œuvre, mais avoir la mise en œuvre réelle est encore plus facile !

48voto

Loïc Février Points 3016

Exemple parfait pour utiliser un tri topologique :

http://en.wikipedia.org/wiki/Topological_sorting

Il vous donnera exactement ce dont vous avez besoin.

13 votes

J'ai trouvé une implémentation C# de tsort : tawani.blogspot.com/2009/02/topologique-tri-et-cyclique.html

19voto

sinelaw Points 6641

Il y a un nuget pour cela .

Pour ceux d'entre nous qui préfèrent ne pas réinventer la roue : utilisez nuget pour installer le fichier QuickGraph .NET, qui comprend de nombreux algorithmes de graphes, notamment tri topologique .

Pour l'utiliser, vous devez créer une instance de AdjacencyGraph<,> comme AdjacencyGraph<String, SEdge<String>> . Ensuite, si vous incluez les extensions appropriées :

using QuickGraph.Algorithms;

Vous pouvez appeler :

var sorted = myGraph.TopologicalSort();

Pour obtenir une liste de nœuds triés.

14voto

Krumelur Points 8935

J'ai aimé la réponse de DMM, mais elle suppose que les nœuds d'entrée sont des feuilles (ce qui peut ou non être ce qui est attendu).

J'affiche une solution alternative utilisant LINQ qui ne fait pas cette supposition. En outre, cette solution utilise yield return pour pouvoir retourner rapidement les feuilles (en utilisant par exemple 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;
    }
}

0 votes

Il s'agit de l'implémentation de tri topologique la plus compacte que j'ai vue jusqu'à présent.

1 votes

"mais il suppose que les noeuds d'entrée sont des feuilles" Je ne comprends pas. Pouvez-vous expliquer ?

1 votes

@osexpert, c'est ainsi que fonctionne l'algorithme : nous devons toujours travailler avec des feuilles - des nœuds qui ne dépendent d'aucun autre nœud. Ce code s'en assure : var elem = elems.FirstOrDefault(x => x.Value.Count == 0); . En particulier, x.Value.Count == 0 assure qu'il n'y a pas de dépendances pour le nœud donné.

6voto

Thang Tran Points 67

C'est ma propre réimplémentation du tri topologique, l'idée est basée sur http://tawani.blogspot.com/2009/02/topological-sorting-and-cyclic.html (Le code source Java porté consomme trop de mémoire, vérifier 50k objets coûte 50k*50k*4 = 10GB ce qui est inacceptable. De plus, il y a encore des conventions de codage Java à certains endroits).

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.")
        {            
        }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X