5

Let say we have a class

Category
{
   ID,
   Name,
   ParentID
}

and a List

1, 'Item 1', 0
2, 'Item 2', 0
3, 'Item 3', 0
4, 'Item 1.1', 1
5, 'Item 3.1', 3
6, 'Item 1.1.1', 4
7, 'Item 2.1', 2

Can we using LINQ to render a tree like:

Item 1
 Item 1.1
  Item 1.1.1
Item 2
 Item 2.1
Item 3
 Item 3.1

Any help is appreciated!

John Woo
  • 258,903
  • 69
  • 498
  • 492
ByulTaeng
  • 1,269
  • 1
  • 21
  • 40

6 Answers6

5

Here's the "LINQ-only" version:

Func<int, int, string[]> build = null;
build = (p, n) =>
{
    return (from x in categories
            where x.ParentID == p
            from y in new[]
            {
                "".PadLeft(n)+ x.Name
            }.Union(build(x.ID, n + 1))
            select y).ToArray();
};
var lines = build(0, 0);

Yes, it's recursive LINQ.


Per NVA's request, here's the way to make all "orphan" records become root records:

Func<IEnumerable<int>, int, string[]> build = null;
build = (ps, n) =>
{
    return (from x in categories
            where ps.Contains(x.ParentID)
            from y in new[]
    {
        "".PadLeft(n)+ x.Name
    }.Union(build(new [] { x.ID }, n + 1))
            select y).ToArray();
};

var roots = (from c in categories
             join p in categories on c.ParentID equals p.ID into gps
             where !gps.Any()
             orderby c.ParentID
             select c.ParentID).Distinct();

var lines = build(roots, 0);
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Btw, If ParentID of an item is not in the list, it'll be excluded, could you help me again? Thanks a lot! – ByulTaeng Nov 05 '10 at 04:36
  • My answer already excludes them if the `ParentID` is not in the list. Are you asking for it to be included? And, if so, do you want these "orphans" to display at the root level (as if their `ParentID` was `0`)? – Enigmativity Nov 05 '10 at 05:32
  • Yes, I want these orphans to display at the root level. Could you suggest me a solution? Thanks – ByulTaeng Nov 05 '10 at 06:57
4

These extension methods do exactly what you want:

public static partial class LinqExtensions
{
    public class Node<T>
    {
        internal Node() { }

        public int Level { get; internal set; }
        public Node<T> Parent { get; internal set; }
        public T Item { get; internal set; }
        public IList<Node<T>> Children { get; internal set; }
    }

    public static IEnumerable<Node<T>> ByHierarchy<T>(
        this IEnumerable<T> source,
        Func<T, bool> startWith, 
        Func<T, T, bool> connectBy)
    {
        return source.ByHierarchy<T>(startWith, connectBy, null);
    }

    private static IEnumerable<Node<T>> ByHierarchy<T>(
        this IEnumerable<T> source,
        Func<T, bool> startWith,
        Func<T, T, bool> connectBy,
        Node<T> 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<Node<T>>();
            Node<T> newNode = new Node<T>
            {
                Level = level,
                Parent = parent,
                Item = value,
                Children = children.AsReadOnly()
            };

            foreach (Node<T> subNode in source.ByHierarchy<T>(possibleSub => connectBy(value, possibleSub),
                                                              connectBy, newNode))
            {
                children.Add(subNode);
            }

            yield return newNode;
        }
    }

    public static void DumpHierarchy<T>(this IEnumerable<Node<T>> nodes, Func<T, string> display)
    {
        DumpHierarchy<T>(nodes, display, 0);
    }

    private static void DumpHierarchy<T>(IEnumerable<LinqExtensions.Node<T>> nodes, Func<T, string> display, int level)
    {
        foreach (var node in nodes)
        {
            for (int i = 0; i < level; i++) Console.Write("  ");
            Console.WriteLine (display(node.Item));
            if (node.Children != null)
                DumpHierarchy(node.Children, display, level + 1);
        }
    }

}

You can use them as follows:

categories.ByHierarchy(
        cat => cat.ParentId == null, // assuming ParentId is Nullable<int>
        (parent, child) => parent.Id == child.ParentId)
     .DumpHierarchy(cat => cat.Name);
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
0
   @model List<OrgChart.Models.Node>
   @{    

    Func<int?, List<OrgChart.Models.Node>, string> recuresive = null;
recuresive = (parentid, list) => string.Join("", list.Where(x => x.ParentId == parentid).Select(x => "<li>" + x.Name + "<ul>" + recuresive(x.Id, list.Where(y => y.ParentId != parentid).ToList()) + "</ul></li>"));
   }
  @Html.Raw("<ul id='org1' >" + recuresive(null, Model) + "</ul>")
  <div id="chart" class="orgChart"></div>
arjthakur
  • 61
  • 6
0

You can use recursion:

public class Category
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int ParentID { get; set; }
    public List<Category> Children { get; set; }
}

class Program
{
    static void Main()
    {
        List<Category> categories = new List<Category>()
        {
            new Category () { ID = 1, Name = "Item 1", ParentID = 0},
            new Category() { ID = 2, Name = "Item 2", ParentID = 0 },
            new Category() { ID = 3, Name = "Item 3", ParentID = 0 },
            new Category() { ID = 4, Name = "Item 1.1", ParentID = 1 },
            new Category() { ID = 5, Name = "Item 3.1", ParentID = 3 },
            new Category() { ID = 6, Name = "Item 1.1.1", ParentID = 4 },
            new Category() { ID = 7, Name = "Item 2.1", ParentID = 2 }
        };

        List<Category> hierarchy = new List<Category>();                        
        hierarchy = categories
                        .Where(c => c.ParentID == 0)
                        .Select(c => new Category() { ID = c.ID, Name = c.Name, ParentID = c.ParentID, Children = GetChildren(categories, c.ID) })
                        .ToList();

        HieararchyWalk(hierarchy);            

        Console.ReadLine();
    }        

    public static List<Category> GetChildren(List<Category> categories, int parentId)
    {            
        return categories
                .Where(c => c.ParentID == parentId)
                .Select(c => new Category { ID = c.ID, Name = c.Name, ParentID = c.ParentID, Children = GetChildren(categories, c.ID) })
                .ToList();
    }

    public static void HieararchyWalk(List<Category> hierarchy)
    {
        if (hierarchy != null)
        {
            foreach (var item in hierarchy)
            {
                Console.WriteLine(string.Format("{0} {1}", item.ID, item.Name));
                HieararchyWalk(item.Children);                    
            }
        }
    }        
}
Branimir
  • 4,327
  • 1
  • 21
  • 33
  • any idea how to ouput his as ordered/unordered html list? – Subliminal Hash Aug 02 '19 at 08:30
  • It depends on what you are trying to accomplish. This example is just a console app. If you just want to print out some Html code in a console window, then you should update HieararchyWalk method to print out valid Html. If you are building web front end application, you are probably using some framework, so you should bind this model somehow to your frontend, it strongly depends on the front-end framework you are using. – Branimir Aug 02 '19 at 10:47
0
 public IEnumerable<HelpPageMenuItem> GetHelpPageMenuItems()
    {
        var helpPages = (from h in Context.HelpPages select new HelpPageMenuItem{HelpPageId = h.HelpPageId, ParentHelpPageId = h.ParentHelpPageId, PageContext = h.PageContext, MenuText = h.MenuText}).ToList();
        var parents = from h in helpPages where !h.ParentHelpPageId.HasValue select PopulateChildren(h, helpPages);
        return parents.ToList();
    }

    private static HelpPageMenuItem PopulateChildren(HelpPageMenuItem helpPageMenuItem, IEnumerable<HelpPageMenuItem> helpPages)
    {
        helpPageMenuItem.ChildHelpPages =
            (from h in helpPages
             where h.ParentHelpPageId == helpPageMenuItem.HelpPageId
             select PopulateChildren(h, helpPages)).ToList();

        return helpPageMenuItem;
    }
jeffreychi
  • 199
  • 3
  • 14
0
       public static List<TSource> BuildTreeView<TSource, TKey>(this List<TSource> allItems
        , Func<TSource, TKey> parentSelector, Func<TSource, TKey> childSelector, Expression<Func<TSource, List<TSource>>> childrenPropertySelector
        , Func<TSource, bool> GetRoot, List<TSource> rootList = null)
    {

        if (rootList == null)
            rootList = allItems.Where(GetRoot).ToList();
        if (rootList != null && rootList.Count > 0)
        {
            rootList.ForEach(rootItem =>
            {
                Func<TSource, bool> whereClause = x => childSelector(rootItem).Equals(parentSelector(x));
                var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as System.Reflection.PropertyInfo;
                var childrenList = allItems.Where(whereClause).ToList();
                childrenProperty.SetValue(rootItem, childrenList);

                if (childrenList.Count > 0)
                    BuildTreeView(allItems, parentSelector, childSelector, childrenPropertySelector, GetRoot, childrenProperty.GetValue(rootItem) as List<TSource>);
            });

        }
        return rootList;
    }

//Call method

List<Channel> rootChannel = listChannel.BuildTreeView(f => f.PARENT_CODE, x => x.CODE, z => z.SubChannels, c => c.CODE == "AC");