Когда вы извлекаете каждый элемент из базы данных, вам нужно добавить его в родительский элемент. Так что держите словарь, чтобы помочь найти родителя. Если вы получаете ребенка до его родителя, вы можете вставить заполнитель, пока не получите реальную вещь.
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 () для использования в качестве заполнителя в случае, если дочерний элемент извлекается до его родителя. Этого требования можно избежать, только если вы что-то знаете о порядке объектов или если ваш словарь обрабатывается в два прохода, как показано в других ответах.
Я использую значение стража _parentGroupName, чтобы узлы верхнего уровня оставались в той же коллекции, что и все остальные. Вы можете легко изменить это, чтобы использовать отдельную коллекцию для узлов верхнего уровня вместо этого.