web-dev-qa-db-fra.com

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 (article) et le second est une collection d'objets du même type dont dépend le premier. Je veux obtenir un List<Item> dans l'ordre des dépendances, donc il n'y a pas d'éléments qui dépendent du premier élément, etc. (pas de dépendance cyclée!).

Contribution: 

 Item4 dépend de Item3 et Item5 
 Item3 dépend de Item1 
 Item1 ne dépend d'aucun 
 Item2 dépend de Item4 
 Item5 ne dépend d'aucun 

Résultat:

 Item1 
 Item5 
 Item3 
 Item4 
 Item2 

Je vous remercie.

SOLUTION: 

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

et

exemple sur C # , exemple sur Java (merci à xcud pour d'excellents exemples)

69
garik

Exemple parfait pour utiliser un tri topologique:

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

Cela vous donnera exactement ce dont vous avez besoin.

40
Loïc Février

Après avoir lutté avec cela pendant un moment, 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" );
    }
}
78
Mesmo

Il y a une pépite pour cela.

Pour ceux d'entre nous qui préfèrent ne pas réinventer la roue: utilisez nuget pour installer la bibliothèque QuickGraph .NET, qui inclut plusieurs algorithmes de graphes, notamment topological sort.

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

using QuickGraph.Algorithms;

Tu peux appeler:

var sorted = myGraph.TopologicalSort();

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

18
sinelaw

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

Je poste une solution alternative en utilisant LINQ qui ne fait pas cette hypothèse. De plus, cette solution utilise yield return pour pouvoir renvoyer 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;
    }
}
10
Krumelur

Ceci 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 50 000 objets coûte 50 000 * 50 000 * 4 = 10 Go (ce qui est inacceptable. De plus, il a toujours la convention 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.")
        {            
        }
    }
}
6
Thang Tran

Une idée différente, pour les cas avec un seul "parent" dépendant:

Au lieu de deps, vous stockez les parents.
Vous pouvez donc dire très facilement si un problème est une dépendance d’un autre.
Et utilisez ensuite Comparable<T>, qui revendiquerait les dépendances "inférieure" et la dépendance "supérieure".
Et ensuite, appelez simplement Collections.sort( List<T>, ParentComparator<T>);

Dans le cas d'un scénario à plusieurs parents, une recherche dans l'arborescence serait nécessaire, ce qui ralentirait l'exécution. Mais cela pourrait être résolu par un cache sous la forme d'une matrice de tri A *.

1
Ondra Žižka

J'ai fusionné l'idée de DMM avec l'algorithme de recherche en profondeur d'abord sur Wikipedia. Cela fonctionne parfaitement pour ce dont j'avais besoin.

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);
  }
}
}
1
user1132959

Cela me faciliterait la tâche en stockant les dépendances d'un élément dans l'élément lui-même:

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

Ensuite, étant donné cela, vous pouvez implémenter un délégué de tri personnalisé pour List qui trie en fonction du fait que l'élément donné est contenu ou non dans la liste des dépendances de l'autre:

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

C’est du code refactorisé à partir de post https://stackoverflow.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
    }

}
0
wishmaster35

Je n'aime pas les méthodes récursives, alors DMM est sorti. Krumelur a l'air bien, mais semble utiliser beaucoup de mémoire? Il s'agit d'une méthode alternative basée sur la pile qui semble fonctionner. Utilise la même logique DFS que DMM et j’ai utilisé cette solution à titre de comparaison lors des tests.

    public static IEnumerable<T> TopogicalSequenceDFS<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> deps)
    {
        HashSet<T> yielded = new HashSet<T>();
        HashSet<T> visited = new HashSet<T>();
        Stack<Tuple<T, IEnumerator<T>>> stack = new Stack<Tuple<T, IEnumerator<T>>>();

        foreach (T t in source)
        {
            stack.Clear();
            if (visited.Add(t))
                stack.Push(new Tuple<T, IEnumerator<T>>(t, deps(t).GetEnumerator()));

            while (stack.Count > 0)
            {
                var p = stack.Peek();
                bool depPushed = false;
                while (p.Item2.MoveNext())
                {
                    var curr = p.Item2.Current;
                    if (visited.Add(curr))
                    {
                        stack.Push(new Tuple<T, IEnumerator<T>>(curr, deps(curr).GetEnumerator()));
                        depPushed = true;
                        break;
                    }
                    else if (!yielded.Contains(curr))
                        throw new Exception("cycle");
                }

                if (!depPushed)
                {
                    p = stack.Pop();
                    if (!yielded.Add(p.Item1))
                        throw new Exception("bug");
                    yield return p.Item1;
                }
            }
        }
    }

Voici également une variante BFS plus simple basée sur la pile. Cela produira un résultat différent de celui ci-dessus, mais toujours valide. Je ne suis pas sûr qu'il y ait un avantage à utiliser la variante DFS ci-dessus, mais c'était amusant de la créer.

    public static IEnumerable<T> TopologicalSequenceBFS<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> dependencies)
    {
        var yielded = new HashSet<T>();
        var visited = new HashSet<T>();
        var stack = new Stack<Tuple<T, bool>>(source.Select(s => new Tuple<T, bool>(s, false))); // bool signals Add to sorted

        while (stack.Count > 0)
        {
            var item = stack.Pop();
            if (!item.Item2)
            {
                if (visited.Add(item.Item1))
                {
                    stack.Push(new Tuple<T, bool>(item.Item1, true)); // To be added after processing the dependencies
                    foreach (var dep in dependencies(item.Item1))
                        stack.Push(new Tuple<T, bool>(dep, false));
                }
                else if (!yielded.Contains(item.Item1))
                    throw new Exception("cyclic");
            }
            else
            {
                if (!yielded.Add(item.Item1))
                    throw new Exception("bug");
                yield return item.Item1;
            }
        }
    }

Pour .NET 4.7+, je suggère de remplacer Tuple par ValueTuple pour une utilisation moindre de la mémoire. Dans les anciennes versions .NET, Tuple peut être remplacé par KeyValuePair.

0
osexpert