简体   繁体   中英

Hierarchical data sorting C#

I have a small hierarchy. Example:

entity:

public class MyClass
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int ParentID { get; set; }
}

My hierarchy data look like:

Id = 1  Name = Item1    ParentId = NULL
Id = 2  Name = Item2    ParentId = 1
Id = 3  Name = Item3    ParentId = 2
Id = 4  Name = Item4    ParentId = 2
Id = 5  Name = Item5    ParentId = 3

The problem is I need to sort it that child nodes must be after its immediate parent. The example bellow must look like

Id = 1  Name = Item1    ParentId = NULL
Id = 2  Name = Item2    ParentId = 1
Id = 3  Name = Item3    ParentId = 2
// the elements with parentID = 3
              Id = 5    Name = Item5    ParentId = 3
    //continue
    Id = 4  Name = Item4    ParentId = 2

Any adwices?

Assuming you have a _list of MyClass objects, then sort it first on Name field, then on ParentId field, like shown below using LINQ:

_list.OrderBy(L=>L.Name).ThenBy(L=>L.ParentId);

Hope this may help.

试试这个我假设第一个你想按 parentid 排序,并且在每个 parent 中你想按 id 排序。

myClassList.OrderBy(parent=>parent.ParentId).ThenBy(parent=>parent.Id);

Try this recursive code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass.data = new List<MyClass>() {
                new MyClass() { ID = 1,  Name = "Item1",    ParentID = null},
                new MyClass() { ID = 2,  Name = "Item2",    ParentID = 1 },
                new MyClass() { ID = 3,  Name = "Item3",    ParentID = 2 },
                new MyClass() { ID = 4,  Name = "Item4",    ParentID = 2 },
                new MyClass() { ID = 5,  Name = "Item5",    ParentID = 3 }
            };

            MyClass myClass = new MyClass();
            myClass.GetData(null, 0);
            Console.ReadLine();
        }
    }
    public class MyClass
    {
        public static List<MyClass> data = null;
        public int ID { get; set; }
        public string Name { get; set; }
        public int? ParentID { get; set; }

        public void GetData(int? id, int level)
        {
            List<MyClass> children = data.Where(x => x.ParentID == id).ToList();

            foreach (MyClass child in children)
            {
                Console.WriteLine(" {0} ID : {1}, Name : {2}, Parent ID : {3}", new string(' ',4 * level),child.ID, child.Name, child.ParentID);
                GetData(child.ID, level + 1);
            }

        }
    }
}

Here you have a way to do it. As you can see, I overrode the ToString method and added a few more cases.

public class MyClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? ParentId { get; set; }

    public override string ToString()
    {
        return string.Format("{0}: {1} - {2}", Id, Name, ParentId);
    }
}

class Program
{

    static void Main(string[] args)
    {
        List<MyClass> list = new List<MyClass>();
        list.Add(new MyClass { Id = 1, Name = "Item1", ParentId = null });
        list.Add(new MyClass { Id = 2, Name = "Item2", ParentId = 1 });
        list.Add(new MyClass { Id = 3, Name = "Item3", ParentId = 2 });
        list.Add(new MyClass { Id = 4, Name = "Item4", ParentId = 2 });
        list.Add(new MyClass { Id = 5, Name = "Item5", ParentId = 3 });
        list.Add(new MyClass { Id = 6, Name = "Item6", ParentId = 1 });
        list.Add(new MyClass { Id = 7, Name = "Item7", ParentId = null });
        list.Add(new MyClass { Id = 8, Name = "Item8", ParentId = 2 });
        list.Add(new MyClass { Id = 9, Name = "Item9", ParentId = 6 });
        list.Add(new MyClass { Id = 10, Name = "Item10", ParentId = 7 });

        foreach(var item in list.Where(x => !x.ParentId.HasValue).OrderBy(x => x.Id))
            ProcessItem(item, list, 0);

        Console.ReadKey();

    }

    private static void ProcessItem(MyClass item, List<MyClass> list, int level)
    {
        Console.WriteLine("{0}{1}", new string(' ', level * 2), item.ToString());
        foreach (var subitem in list.Where(x => x.ParentId == item.Id).OrderBy(x => x.Id))
            ProcessItem(subitem, list, level  + 1);
    }
}

Would something like this work for you?

If you need an actual ordered list, try this:

foreach (var item in OrderList(list))
    Console.WriteLine(item.ToString());

(...)

private static List<MyClass> OrderList(List<MyClass> list)
{
    List<MyClass> orderedList = new List<MyClass>(list.Count());
    foreach (var item in list.Where(x => !x.ParentId.HasValue).OrderBy(x => x.Id))
        AddItem(item, list, orderedList);

    return orderedList;
}

private static void AddItem(MyClass item, List<MyClass> list, List<MyClass> orderedList)
{
    orderedList.Add(item);
    foreach (var subitem in list.Where(x => x.ParentId == item.Id).OrderBy(x => x.Id))
        AddItem(subitem, list, orderedList);
}

The following should do the trick (and show some better performance because we save the hierarchy in a lookup, instead of searching the IEnumerable on the fly):

public List<MyClass> SortHierarchically(IEnumerable<MyClass> myClasses)
{
    if(myClasses == null)
        return new List<MyClass>();

    var myClassesByParentId = myClasses.ToLookup(mc => mc.ParentId);
    var result = new List<MyClass>(myClasses.Count());

    int? currentParentId = null;
    MyClass currentItem = myClassesByParentId[currentParentId].Single();
    result.Add(currentItem);
    currentParentId = currentItem.Id;

    if(myClassesByParentId.Contains(currentParentId))
        result.AddRange(myClassesByParentId[currentParentId].SelectMany(mc => GetAllSortedChildren(mc, myClassesByParentId)));  

    return result;
}

public List<MyClass> GetAllSortedChildren(MyClass parent, ILookup<int?, MyClass> myClassesByParentId)
{
    var result = new List<MyClass>() { parent };
    if(myClassesByParentId.Contains(parent.Id))
        retsult.AddRange(myClassesByParentId[parent.Id].SelectMany(mc => GetAllSortedChildren(mc, myClassesByParentId)));

    return result;
}

It would be interesting to find a method of sorting this by standard LINQ, with some clever comparer or such.

One of the answers above works well. This is a generic version.

public static class SortingMethods
{
    public static IList<T> OrderByHierarchical<T>(
        this IEnumerable<T> items,
        Func<T, string> getId,
        Func<T, string> getParentId)
    {
        if (items == null)
            return new List<T>();

        var itemsByParentId = items.ToLookup(item => getParentId(item));
        var result = new List<T>(items.Count());

        var currentParentId = "";
        var currentItem = itemsByParentId[currentParentId].Single();
        result.Add(currentItem);
        currentParentId = getId(currentItem);

        if (itemsByParentId.Contains(currentParentId))
            result.AddRange(itemsByParentId[currentParentId].SelectMany(item => GetAllSortedChildren(item, itemsByParentId, getId)));

        return result;
    }

    private static IList<T> GetAllSortedChildren<T>(T parent, ILookup<string, T> itemsByParentId, Func<T, string> getId)
    {
        var result = new List<T>() { parent };

        if (itemsByParentId.Contains(getId(parent)))
        {
            result.AddRange(itemsByParentId[getId(parent)].SelectMany(item => GetAllSortedChildren(item, itemsByParentId, getId)));
        }

        return result;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM