As you extract each element from the database, you need to add it to its parent. So keep a Dictionary to help find the parent. If you get a child before its parent, then you can insert a placeholder until you get the real thing.
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;
}
}
The dictionary element at _parentGroupName will then be a dummy node whose children are all of your top-level groups (i.e. the ones with NULL as ParentID from the database); from that element you can do a recursive traversal:
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 + " " );
}
}
}
NOTE: This implementation runs in a single inline pass through the data -- you can add elements immediately as they're retrieved from the database, and you only need to make a single pass through the data. That means you save some time and some memory. But in return, it requires that you be able to allocate an object with type IGroup() to act as a placeholder, in case a child is retrieved before its parent. You can only avoid that requirement if you know something about the order of the objects or if you process your dictionary in two passes, as is shown in the other answers.
I use a sentinel value, _parentGroupName, to keep the top-level nodes in the same collection as all the others. You can easily alter this to use a separate collection for the top level nodes instead if you prefer.