using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Developpez.Dotnet.Collections { public static partial class EnumerableExtensions { #region ToHierarchy /// /// Représente un noeud dans une hiérarchie d'objets /// /// Type des objets de la hiérarchie public class Node { internal Node() { } /// /// Objet contenu par ce noeud /// public T Item { get; internal set; } /// /// Niveau du noeud dans la hiérarchie (0 pour un noeud racine) /// public int Level { get; internal set; } /// /// Noeud parent dans la hiérarchie (null pour un noeud racine) /// public Node Parent { get; internal set; } /// /// Noeuds enfants dans la hiérarchie /// public IList> Children { get; internal set; } } /// /// Renvoie une collection de noeuds représentant les objets de la collection source sous forme hiérarchique, /// selon la relation spécifiée /// /// Type des éléments de la collection source /// Collection d'objets à hiérarchiser /// Prédicat pour identifier les éléments racines /// Relation qui lie un objet parent à ses enfants /// Une collection de noeuds représentant les objets de la collection source sous forme hiérarchique public static IEnumerable> ToHierarchy( this IEnumerable source, Func startWith, Func connectBy) { return source.ToHierarchy(startWith, connectBy, null); } private static IEnumerable> ToHierarchy( this IEnumerable source, Func startWith, Func connectBy, Node parent) { int level = (parent == null ? 0 : parent.Level + 1); if (source == null) throw new ArgumentNullException("source"); if (startWith == null) throw new ArgumentNullException("startWith"); if (connectBy == null) throw new ArgumentNullException("connectBy"); foreach (T value in from item in source where startWith(item) select item) { var children = new List>(); var newNode = new Node { Level = level, Parent = parent, Item = value, Children = children.AsReadOnly() }; T tmpValue = value; children.AddRange(source.ToHierarchy(possibleSub => connectBy(tmpValue, possibleSub), connectBy, newNode)); yield return newNode; } } #endregion #region DumpHierarchy /// /// Ecrit une hiérarchie d'objets dans le TextWriter spécifié /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// TextWriter dans lequel écrire la hiérarchie /// Chaine d'indentation à utiliser /// Fonction qui sélectionne le membre à afficher public static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer, string indent, Func display) { nodes.DumpHierarchy(writer, indent, display, 0); } /// /// Ecrit une hiérarchie d'objets dans le TextWriter spécifié /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// TextWriter dans lequel écrire la hiérarchie /// Fonction qui sélectionne le membre à afficher public static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer, Func display) { nodes.DumpHierarchy(writer, " ", display, 0); } /// /// Ecrit une hiérarchie d'objets dans le TextWriter spécifié /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// TextWriter dans lequel écrire la hiérarchie /// Chaine d'indentation à utiliser public static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer, string indent) { nodes.DumpHierarchy(writer, indent, item => item.ToString(), 0); } /// /// Ecrit une hiérarchie d'objets dans le TextWriter spécifié /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// TextWriter dans lequel écrire la hiérarchie public static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer) { nodes.DumpHierarchy(writer, " ", item => item.ToString(), 0); } /// /// Ecrit une hiérarchie d'objets dans la console /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// Chaine d'indentation à utiliser /// Fonction qui sélectionne le membre à afficher public static void DumpHierarchy(this IEnumerable> nodes, string indent, Func display) { nodes.DumpHierarchy(Console.Out, indent, display, 0); } /// /// Ecrit une hiérarchie d'objets dans la console /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// Fonction qui sélectionne le membre à afficher public static void DumpHierarchy(this IEnumerable> nodes, Func display) { nodes.DumpHierarchy(Console.Out, " ", display, 0); } /// /// Ecrit une hiérarchie d'objets dans la console /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// Chaine d'indentation à utiliser public static void DumpHierarchy(this IEnumerable> nodes, string indent) { nodes.DumpHierarchy(Console.Out, indent, item => item.ToString(), 0); } /// /// Ecrit une hiérarchie d'objets dans la console /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher public static void DumpHierarchy(this IEnumerable> nodes) { nodes.DumpHierarchy(Console.Out, " ", item => item.ToString(), 0); } private static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer, string indent, Func display, int level) { foreach (var node in nodes) { for (int i = 0; i < level; i++) writer.Write(indent); writer.WriteLine(display(node.Item)); if (node.Children != null) node.Children.DumpHierarchy(writer, indent, display, level + 1); } } #endregion #region Flatten /// /// Aplanit une hiérarchie d'objets de même type en énumérant tous les noeuds de la hiérarchie, dans l'ordre de parcours indiqué. /// Les éléments renvoyés sont créés à partir d'un noeud et de son niveau dans la hiérarchie. /// /// Type des éléments de la hiérarchie /// Type des éléments de la séquence de résultat /// Collection des éléments racines de la hiérarchie /// Fonction qui renvoie les enfants d'un noeud de la hiérarchie /// Mode de parcours de la hiérarchie /// Fonction qui permet de renvoyer un résultat à partir d'un noeud et de son niveau dans la hiérarchie. /// Une séquence contenant tous les noeuds de la hiérarchie public static IEnumerable Flatten(this IEnumerable source, Func> childrenSelector, TreeTraversalMode traversalMode, Func resultSelector) { source.CheckArgumentNull("source"); childrenSelector.CheckArgumentNull("childrenSelector"); switch (traversalMode) { case TreeTraversalMode.DepthFirst: return source.DepthFirstFlattenIterator(childrenSelector, resultSelector); case TreeTraversalMode.BreadthFirst: return source.BreadthFirstFlattenIterator(childrenSelector, resultSelector); default: throw new ArgumentOutOfRangeException("traversalMode"); } } /// /// Aplanit une hiérarchie d'objets de même type en énumérant tous les noeuds de la hiérarchie, dans l'ordre de parcours indiqué. /// /// Type des éléments de la hiérarchie /// Collection des éléments racines de la hiérarchie /// Fonction qui renvoie les enfants d'un noeud de la hiérarchie /// Mode de parcours de la hiérarchie /// Une séquence contenant tous les noeuds de la hiérarchie public static IEnumerable Flatten(this IEnumerable source, Func> childrenSelector, TreeTraversalMode traversalMode) { return Flatten(source, childrenSelector, traversalMode, (x, _) => x); } private static IEnumerable BreadthFirstFlattenIterator(this IEnumerable source, Func> childrenSelector, Func resultSelector) { var queue = new Queue>(source.Select(n => new NodeWithLevel(n, 0))); while (queue.Count > 0) { var item = queue.Dequeue(); yield return resultSelector(item.Node, item.Level); foreach (var child in childrenSelector(item.Node)) { queue.Enqueue(new NodeWithLevel(child, item.Level + 1)); } } } private static IEnumerable DepthFirstFlattenIterator(this IEnumerable source, Func> childrenSelector, Func resultSelector) { var list = new LinkedList>(source.Select(n => new NodeWithLevel(n, 0))); while (list.Count > 0) { var current = list.First.Value; list.RemoveFirst(); yield return resultSelector(current.Node, current.Level); var llNode = list.First; foreach (var child in childrenSelector(current.Node)) { var newNode = new NodeWithLevel(child, current.Level + 1); if (llNode != null) list.AddBefore(llNode, newNode); else list.AddLast(newNode); } } } private class NodeWithLevel { private readonly TNode _node; private readonly int _level; public NodeWithLevel(TNode node, int level) { _node = node; _level = level; } // ReSharper disable MemberHidesStaticFromOuterClass public TNode Node // ReSharper restore MemberHidesStaticFromOuterClass { get { return _node; } } public int Level { get { return _level; } } } #endregion } }