views:

1423

answers:

4

given the following class ...

public class Category {
  public string Name {get;set;}
  public Category ParentCategory {get;set;}
}

What the most efficient way to output the following from a collection (IList<Category>) of Category objects?

+ Parent Category
++ Sub Category
++ Sub Category 2
+ Parent Category 2
++ Sub ...
++ Sub ..
++ Sub ....

EDIT: Perhaps the real question should be, how should I represent this model in the database and retrieve it using NHibernate?

+3  A: 

You may wish to consider reversing your relationship. If a node can get to its parent but not vice versa, you have to have all the leaf nodes in order to print out the full tree. Compare this to the situation where you have each node know about its children - then you only need the root node.

Jon Skeet
Agreed, unfortunately I'm not sure I can do that with my data access tool (NHibernate). If I could, I would in a second.
Kyle West
Okay. Will reconsider when I'm back online - but that may be a few hours. Hopefully you'll have good answers by then :)Do you have any ordering between categories? (I suppose you could always use name...) That might be helpful.
Jon Skeet
A: 

recently i read about Hierarchical Linq Queries, check out http://weblogs.asp.net/okloeten/archive/2006/07/09/Hierarchical-Linq-Queries.aspx

but I tend to aggree with Jon Skeet.

Student for Life
+2  A: 

A small recursive function can do it for you.

static void recurseCategories(ref List<Category> cl, Category start, int level)
{
  foreach (Category child in cl)
  {
    if (child.ParentCategory == start)
    {
      Console.WriteLine(new String(' ', level) + child.Name);
      recurseCategories(ref cl, child, level + 1);
    }
  }
}

My assumptions were:

  • You've got an List of Category. (Of course all Category objects you want to print must be in that list. I thought that was self-evident, but seemingly it was not.)
  • The root category has a parent of null. The initial function call should therefore be recurseCategories(ref myCategoryList, null, 0).
  • No orphaned elements exist in your list. Some error handling code should be added.
  • Output order will be coherent to whatever order the list is iterated, so apart from the hierarchy it's more or less coincidental.
Tomalak
That won't work because his elements can only see their parent, but the parent cannot see its children.
FlySwat
So to clarify, you can walk from one child up to root, but you won't see any other branches of the tree.
FlySwat
But it just worked for me. Feeding it a list of Categories yielded a nicely intended category tree.
Tomalak
This works if the List<Category> contains all categories - children and parents. HOwever, if it only contains parents, it won't - but then what would? You'd need another collection of children, and then a variant of this example would work.
Jeff Yates
I said so. The question author did not indicate differently. Why do you assume this would not be the case?
Tomalak
Because that is an absolutely ridiculousness way to store a node datastructure?
FlySwat
I see. Since you seem to know how to do it right, make a proposal.
Tomalak
His problem is that he doesn't need a list, only the reference to the root node, and an array in each node of children.
FlySwat
Agreed. But assume you don't have that data structure from the start, and all you need is indented output. To build a nodes-with-array-of-children structure a similar process would be necessary, so why do it twice?
Tomalak
A: 

@Tomalok, when I run your code, with this data structure, I get two nodes:

class Program
{
    static void Main(string[] args)
    {
        var foo = new List<Category>();

        // Example structure from Question
        var fail1 = new Category() { Name = "test1" };
        var fail2 = new Category() { Name = "test2", ParentCategory = fail1 };
        var fail3 = new Category() { Name = "test3", ParentCategory = fail1 };
        var fail4 = new Category() { Name = "test4"};
        var fail5 = new Category() { Name = "test5", ParentCategory = fail4 };
        var fail6 = new Category() { Name = "test6", ParentCategory = fail4 };
        var fail7 = new Category() { Name = "test7", ParentCategory = fail4 };

        foo.Add(fail1);
        foo.Add(fail4);

        recurseCategories(ref foo, null, 0);

    }

    static void recurseCategories(ref List<Category> cl, Category start, int level)
    {
        foreach (Category child in cl)
        {
            if (child.ParentCategory == start)
            {
                Console.WriteLine(new String(' ', level) + child.Name);
                recurseCategories(ref cl, child, level + 1);
            }
        }
    }

    public class Category
    {
        public string Name { get; set; }
        public Category ParentCategory { get; set; }
    }

}

Output:

test1
test4

Output should be:

test1
   test 2
   test 3
test 4
   test 5
   test 6
   test 7

This is because there is no relationship between child nodes that share the same parent.

FlySwat
Jonathan: In my example of course *all* of the nodes are in the List. Why would you add only two of them?
Tomalak
@Jonathan: Tomalak is correct here. THe question states that a list of categories is provided - it doesn't state that list only contains parent categories. Assuming it contains ALL categories, including parents and children, Tomalak's code is correct.
Jeff Yates
Why the world would you collapse a node structure into an array?
FlySwat
Why even have parent references at that point? if your going to convert into an array, you might as well just use a count argument for how deep you are in the hierarchy and keep things in order...In other words, He (and I guess you) are doing it wrong.
FlySwat
Maybe because they are stored in a DB table with an Id / ParentId structure and all you have after you select them is a *list of nodes*?
Tomalak
So read them in properly when you convert your dataset into a list? I'm not going to argue the semantics of it, his design is very flawed, your code works with it...I'd never use either? Ok?
FlySwat
Who said you should? No need to get pissed.
Tomalak