C# - 递归分组

时间:2009-11-28 15:06:34

标签: c# .net recursion

我有一个IGroup类型的对象列表。这些可以嵌套到umlimited级别,我试图在从数据库中检索它们之后对它们进行分组。我无法理解如何递归地将所有组添加到正确的父母。任何以null作为父级的组都是顶级组。我不能保证他们从数据库中出来的顺序。

public interface IGroup {
  string ID { get; set; }
  string Name { get; set; }
  string ParentID { get; set; }
  IList<IGroup> Groups { get; set; }
  ...

所以,如果我有一个列表:

Group1: ID = g1, ParentID = null
Group1a: ID = g2, ParentID = g1
Group2: ID = g3, ParentID = null
Group1b: ID = g4, ParentID = g3
Group1bc: ID = g5, ParentID = g4

我正在尝试将它们分组为:

|Group1
|--Group1a
|--Group1b
|--|
   |--Group1bc
|Group2

有人想要递归地将它们分组吗?

6 个答案:

答案 0 :(得分:6)

无需递归。即:

var lookup = items.ToDictionary(g => g.ID); // items is IEnumerable<IGroup>
foreach (var item in items.Where(g => g.ParentID != null)) {
    lookup[item.ParentID].Groups.Add(item);
}
var parents = items.Where(g => g.ParentID == null);

请注意,如果lookup[item.ParentID]没有相应的IGroupParentID将会抛出。您可以使用TryGetValue更优雅地处理此问题。

我对IGroup的实施:

public class Group : IGroup {
    public string ID { get; set; }
    public string Name { get; set; }
    public string ParentID { get; set; }
    public IList<IGroup> Groups { get; set; }
    public Group() {
        Groups = new List<IGroup>();
    }
}

我的测试项目:

IEnumerable<IGroup> items = new List<IGroup>() {
    new Group() { ID = "g1", ParentID = null },
    new Group() { ID = "g2", ParentID = "g1" },
    new Group() { ID = "g3", ParentID = null },
    new Group() { ID = "g4", ParentID = "g3" },
    new Group() { ID = "g5", ParentID = "g4" },
    new Group() { ID = "g6", ParentID = "g5" }
};    

答案 1 :(得分:0)

您可以尝试按父ID排序,假设父组始终在子组之前创建。

答案 2 :(得分:0)

ParentID分组(Linq:GroupBy),按ID排序。

从空的根节点(ID:null)开始,并添加具有此ParentID的所有项目。对已添加的任何项目递归地继续此过程。

答案 3 :(得分:0)

从数据库中提取每个元素时,需要将其添加到其父元素中。所以保持一个字典来帮助找到父母。如果你的孩子在其父母之前得到了孩子,那么你可以插入一个占位符,直到你得到真实的东西。

void BuildGroups()
{
   foreach( IGroup x /* obtained from database or a collection or wherever */ )
      AddGroup( x );
}

Dictionary<string,IGroup> _groups = new Dictionary<string,IGroup>;
string _parentGroupName = "PARENT";
void AddGroup( IGroup x )
{
    // locate (or create) parent and add incoming group
    IGroup parent;
    string parentID = x.ParentID ?? _parentGroupName;
    if( !groups.TryGetValue( parentID, out parent ) )
    {
       parent = new Group( parentID ); // SEE NOTE BELOW!
       _groups[parentID] = parent;
    }
    parent.Groups.Add( x );

    // locate (or insert) child, and make sure it's up to date
    IGroup child;
    if( groups.TryGetValue( x.ID, out child ) )
    {
       // We must have inserted this ID before, because we found one
       // of ITS children first.  If there are any other values besides
       // ParentID and ID, then copy them from X to child here.
    }
    else
    {
       // first time we've seen this child ID -- insert it
        _groups[x.ID] = x;
    }

}

_parentGroupName的字典元素将是一个虚拟节点,其子节点都是您的顶级组(即数据库中具有NULL作为ParentID的那些);从该元素中,您可以进行递归遍历:

VisitGroups( _groups[_parentGroupName], "" );

void VisitGroups( string ID, string indent )
{
   IGroup x;
   if( _groups.TryGetValue( ID, out x ) )
   {
       foreach( IGroup y in x.Groups )
       {
          WriteLine( indent + " {0}", y.ID );
          VisitGroups( y.ID, indent + "   " );
       }        
   }
}

注意:此实现在单个内联传递数据中运行 - 您可以在从数据库中检索元素时立即添加元素,并且您只需要对数据进行一次传递。这意味着你节省了一些时间和一些记忆。但作为回报,它要求您能够分配具有IGroup()类型的对象作为占位符,以防在其父级之前检索子级。如果您对对象的顺序有所了解,或者如果您在两遍中处理字典,则只能避免该要求,如其他答案所示。

我使用sentinel值_parentGroupName将顶级节点保存在与其他所有节点相同的集合中。如果您愿意,可以轻松地对此进行更改,以便为顶级节点使用单独的集合。

答案 4 :(得分:0)

这不是递归的,但这是一个解决方案(假设您在名为groups的列表中拥有所有组)

var rootGroups = new List<IGroup>();
var dic = groups.ToDictionary(g => g.ID);
foreach (var g in groups)
{
    if (g.ParentID == null)
    {
        rootGroups.Add(g);
    }
    else
    {
        IGroup parent;
        if (dic.TryGetValue(g.ParentID, out parent))
        {
                parent.Groups.Add(g);
        }
    }
}

答案 5 :(得分:0)

你可以试试这个

public interface IGroup
{
    string ID { get; set; }  
    string Name { get; set; }  
    string ParentID { get; set; }  
    List<IGroup> Groups { get; set; }
}
public class Group : IGroup
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string ParentID { get; set; }
    public List<IGroup> Groups { get; set; }
    public Group()
    {

    }
    public Group(string id, string name, List<IGroup> childs)
    {
        ID = id;
        Name = name;
        Groups = (List<IGroup>)childs.Cast<IGroup>();
    }

}


class Program
{
    static void Main(string[] args)
    {
        List<IGroup> OriginalList;
        List<IGroup> HirarchList = new List<IGroup>();

        OriginalList = new List<IGroup>() 
        { 
            new Group() { ID = "g1", ParentID = null }, 
            new Group() { ID = "g2", ParentID = "g1" }, 
            new Group() { ID = "g3", ParentID = null }, 
            new Group() { ID = "g4", ParentID = "g3" }, 
            new Group() { ID = "g5", ParentID = "g4" }, 
            new Group() { ID = "g6", ParentID = "g5" } };

        HirarchList = GetCreateList(null,  OriginalList);
    }
    public static List<IGroup> GetCreateList(string id, List<IGroup> list)
    {
        List<IGroup> temp = new List<IGroup>();
        temp = (from item in list 
               where item.ParentID == id
               select (IGroup)new Group(item.ID, item.Name,GetCreateList(item.ID, list))).ToList();

        return (List<IGroup>)temp;
    }

}