web-dev-qa-db-fra.com

Comment imprimer une arborescence?

J'essaie d'améliorer les performances de notre application. J'ai des informations sur les performances sous la forme d'une arborescence d'appels, avec la classe de nœud suivante:

public class Node
{
    public string Name; // method name
    public decimal Time; // time spent in method
    public List<Node> Children;
}

Je veux imprimer l’arbre de manière à ce que je puisse voir des lignes entre les nœuds - quelque chose comme dans cette question . Qu'est-ce qu'un algorithme que je peux utiliser en C # pour le faire?

Edit: Évidemment, je dois utiliser la récursivité, mais mes tentatives n'arrêtent pas de placer les lignes au mauvais endroit. Ce que je demande, c’est un algorithme spécifique qui permettra d’imprimer l’arbre de manière agréable - les détails indiquant quand imprimer une ligne verticale et quand imprimer une ligne horizontale.

Edit: Il ne suffit pas d’utiliser des copies d’une chaîne pour indenter les nœuds. Je ne cherche pas

A
|-B
|-|-C
|-|-D
|-|-|-E
|-F
|-|-G

il doit être

A
+-B
| +-C
| +-D
|   +-E
+-F
  +-G

ou quelque chose de similaire, tant que l'arborescence est visible. Notez que C et D sont mis en retrait différemment de G - Je ne peux pas simplement utiliser une chaîne répétée pour mettre en retrait les nœuds.

45
Simon

L'astuce consiste à passer une chaîne en tant qu'indent et à traiter spécialement le dernier enfant:

class Node
{    
   public void PrintPretty(string indent, bool last)
   {
       Console.Write(indent);
       if (last)
       {
           Console.Write("\\-");
           indent += "  ";
       }
       else
       {
           Console.Write("|-");
           indent += "| ";
       }
       Console.WriteLine(Name);

       for (int i = 0; i < Children.Count; i++)
           Children[i].PrintPretty(indent, i == Children.Count - 1);
   }
}

Si appelé comme ça:

root.PrintPretty("", true);

affichera dans ce style:

\-root
  \-child
    |-child
    \-child
      |-child
      |-child
      \-child
        |-child
        |-child
        | |-child
        | \-child
        |   |-child
        |   |-child
        |   |-child
        |   \-child
        |     \-child
        |       \-child
        \-child
          |-child
          |-child
          |-child
          | \-child
          \-child
            \-child
74
Will

Avec récursion

Vous devrez suivre une chaîne d'indentation modifiée au fur et à mesure que vous avancez dans l'arbre. Pour éviter d'ajouter des caractères | supplémentaires, vous devez également savoir si le nœud est le dernier enfant de cet ensemble.

public static void PrintTree(Node tree, String indent, Bool last)
{
    Console.Write(indent + "+- " + tree.Name);
    indent += last ? "   " : "|  ";

    for (int i == 0; i < tree.Children.Count; i++)
    {
        PrintTree(tree.Children[i], indent, i == tree.Children.Count - 1);
    }
}

Quand appelé comme ça:

PrintTree(node, "", true)

Il produira un texte comme ceci:

+- root
   +- branch-A
   |  +- sibling-X
   |  |  +- grandchild-A
   |  |  +- grandchild-B
   |  +- sibling-Y
   |  |  +- grandchild-C
   |  |  +- grandchild-D
   |  +- sibling-Z
   |     +- grandchild-E
   |     +- grandchild-F
   +- branch-B
      +- sibling-J
      +- sibling-K

Sans récursion

Si vous avez une arborescence very deep et que la taille de la pile d'appels est limitée, vous pouvez effectuer une traversée d'arborescence statique et non récursive pour obtenir le même résultat:

public static void PrintTree(Node tree)
{
    List<Node> firstStack = new List<Node>();
    firstStack.Add(tree);

    List<List<Node>> childListStack = new List<List<Node>>();
    childListStack.Add(firstStack);

    while (childListStack.Count > 0)
    {
        List<Node> childStack = childListStack[childListStack.Count - 1];

        if (childStack.Count == 0)
        {
            childListStack.RemoveAt(childListStack.Count - 1);
        }
        else
        {
            tree = childStack[0];
            childStack.RemoveAt(0);

            string indent = "";
            for (int i = 0; i < childListStack.Count - 1; i++)
            {
                indent += (childListStack[i].Count > 0) ? "|  " : "   ";
            }

            Console.WriteLine(indent + "+- " + tree.Name);

            if (tree.Children.Count > 0)
            {
                childListStack.Add(new List<Node>(tree.Children));
            }
        }
    }
}
21
Joshua Stachowski

Créez la méthode PrintNode et utilisez la récursivité:

class Node
{
    public string Name;
    public decimal Time;
    public List<Node> Children = new List<Node>();

    public void PrintNode(string prefix)
    {
        Console.WriteLine("{0} + {1} : {2}", prefix, this.Name, this.Time);
        foreach (Node n in Children)
            if (Children.IndexOf(n) == Children.Count - 1)
                n.PrintNode(prefix + "    ");
            else
                n.PrintNode(prefix + "   |");
    }
}

Puis, pour imprimer l’arbre entier, exécutez simplement:

topNode.PrintNode("");

Dans mon exemple, cela nous donnerait quelque chose comme ça:

 + top : 123
   | + Node 1 : 29
   |   | + subnode 0 : 90
   |   |     + sdhasj : 232
   |   | + subnode 1 : 38
   |   | + subnode 2 : 49
   |   | + subnode 8 : 39
   |     + subnode 9 : 47
     + Node 2 : 51
       | + subnode 0 : 89
       |     + sdhasj : 232
       | + subnode 1 : 33
         + subnode 3 : 57
9
Gacek

Voici une variante de la réponse (actuellement acceptée) de @Will. Les changements sont les suivants:

  1. Ceci utilise des symboles Unicode au lieu de ASCII pour une apparence plus agréable.
  2. L'élément racine n'est pas en retrait.
  3. Une dernière ligne 'vierge' est ajoutée au dernier enfant d'un groupe (ce qui facilite l'analyse visuelle).

Présenté sous forme de pseudo-code pour une consommation plus facile en dehors de C++:

def printHierarchy( item, indent )
  kids = findChildren(item)  # get an iterable collection
  labl = label(item)         # the printed version of the item
  last = isLastSibling(item) # is this the last child of its parent?
  root = isRoot(item)        # is this the very first item in the tree?

  if root then
    print( labl )
  else
    # Unicode char U+2514 or U+251C followed by U+2574
    print( indent + (last ? '└╴' : '├╴') + labl )

    if last and isEmpty(kids) then
      # add a blank line after the last child
      print( indent ) 
    end

    # Space or U+2502 followed by space
    indent = indent + (last ? '  ' : '│ ')
  end

  foreach child in kids do
    printHierarchy( child, indent )
  end
end

printHierarchy( root, "" )

Exemple de résultat:

Body
├╴PaintBlack
├╴CarPaint
├╴Black_Material
├╴PaintBlue
├╴Logo
│ └╴Image
│
├╴Chrome
├╴Plastic
├╴Aluminum
│ └╴Image
│
└╴FabricDark
6
Phrogz

j'utilise la méthode suivante pour imprimer un fichier BST

private void print(Node root, String prefix) {
    if (root == null) {
    System.out.println(prefix + "+- <null>");
    return;
    }

    System.out.println(prefix + "+- " + root);
    print(root.left, prefix + "|  ");
    print(root.right, prefix + "|  ");
}

Voici la sortie.

+- 43(l:0, d:1)
|  +- 32(l:1, d:3)
|  |  +- 10(l:2, d:0)
|  |  |  +- <null>
|  |  |  +- <null>
|  |  +- 40(l:2, d:2)
|  |  |  +- <null>
|  |  |  +- 41(l:3, d:0)
|  |  |  |  +- <null>
|  |  |  |  +- <null>
|  +- 75(l:1, d:5)
|  |  +- 60(l:2, d:1)
|  |  |  +- <null>
|  |  |  +- 73(l:3, d:0)
|  |  |  |  +- <null>
|  |  |  |  +- <null>
|  |  +- 100(l:2, d:4)
|  |  |  +- 80(l:3, d:3)
|  |  |  |  +- 79(l:4, d:2)
|  |  |  |  |  +- 78(l:5, d:1)
|  |  |  |  |  |  +- 76(l:6, d:0)
|  |  |  |  |  |  |  +- <null>
|  |  |  |  |  |  |  +- <null>
|  |  |  |  |  |  +- <null>
|  |  |  |  |  +- <null>
|  |  |  |  +- <null>
|  |  |  +- <null>
4
KSC

Ceci est une version générique de la réponse de Joshua Stachowski. La bonne chose à propos de la réponse de Joshua Stachowski est qu’elle n’a pas besoin de la classe de nœuds elle-même pour implémenter une méthode supplémentaire et qu’elle a également l’air agréable.

J'ai fait sa solution générique qui peut être utilisée pour n'importe quel type sans modifier le code. 

    public static void PrintTree<T>(T rootNode,
                                    Func<T, string> nodeLabel, 
                                    Func<T, List<T>> childernOf)
            {
                var firstStack = new List<T>();
                firstStack.Add(rootNode);

                var childListStack = new List<List<T>>();
                childListStack.Add(firstStack);

                while (childListStack.Count > 0)
                {
                    List<T> childStack = childListStack[childListStack.Count - 1];

                    if (childStack.Count == 0)
                    {
                        childListStack.RemoveAt(childListStack.Count - 1);
                    }
                    else
                    {
                        rootNode = childStack[0];
                        childStack.RemoveAt(0);

                        string indent = "";
                        for (int i = 0; i < childListStack.Count - 1; i++)
                        {
                            indent += (childListStack[i].Count > 0) ? "|  " : "   ";
                        }

                        Console.WriteLine(indent + "+- " + nodeLabel(rootNode));
                        var children = childernOf(rootNode);
                        if (children.Count > 0)
                        {
                            childListStack.Add(new List<T>(children));
                        }
                    }
                }
            }

Usage

 PrintTree(rootNode, x => x.ToString(), x => x.Children);
1
Amit Hasan

Utiliser les coordonnées (y, x)

Code C ici:

void printVLine(wchar_t token, unsigned short height, unsigned short y, unsigned short x);
const static wchar_t TREE_VLINE = L'┃';
const static wchar_t TREE_INBRANCH[] = L"┣╾⟶ ";
const static wchar_t TREE_OUTBRANCH[] = L"┗╾⟶ ";

typedef void (*Printer)(void * whateverYouWant);
const static unsigned int  INBRANCH_SIZE = sizeof(TREE_INBRANCH) / sizeof(TREE_INBRANCH[0]);
const static unsigned int OUTBRANCH_SIZE = sizeof(TREE_OUTBRANCH) / sizeof(TREE_OUTBRANCH[0]);

size_t Tree_printFancy(Tree * self, int y, int x, Printer print){
    if (self == NULL) return 0L;
    //
    size_t descendants = y;
    move(y, x);
    print(Tree_at(self));
    if (!Tree_isLeaf(self)){ // in order not to experience unsigned value overflow in while()
        move(++y, x); 
        size_t i = 0;
        while(i < Tree_childrenSize(self) - 1){
            wprintf(TREE_INBRANCH);
            size_t curChildren = Tree_printFancy(
                   Tree_childAt(self, i), y, x + INBRANCH_SIZE, print
            );
            printVLine(TREE_VLINE, curChildren , y + 1, x);
            move((y += curChildren), x);
            ++i;
        }
        wprintf(TREE_OUTBRANCH); 
        y += Tree_printFancy(       // printing outermost child
            Tree_childAt(self, i), y, x + OUTBRANCH_SIZE, print
        ) - 1;
    }   
    return y - descendants + 1;
}

Elle s’applique plutôt à l’impression sur console. La fonction move (y, x) déplace le curseur à l’emplacement (y, x) de l’écran . La meilleure partie est que vous pouvez modifier le style de sortie en modifiant les variables. .TREE_VLINE, TREE_INBRANCH, TREE_OUTBRANCH, la longueur des deux dernières chaînes n'a pas d'importance. Et vous pouvez imprimer ce que vous voulez en passant le pointeur de la fonction Imprimante, ce qui affichera la valeur du nœud d'arborescence actuel . La sortie ressemble à this

0
Veetaha