Click here to Skip to main content
15,898,732 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
I have a nested class like following:
C#
public class Group
{

    public int Id { get; set; }

    public string Description { get; set; }

    public int ParentId { get; set; }

    public List<Group> Groups { get; set; }

}

I have a List of class Group like
C#
List<Group> gp=new List<Group>();

My requirement is to find all the Id values of list gp using Linq.I tried with Selectmany(), but its returning me only the first level of hirarchy.
Posted
Updated 21-Sep-15 8:46am
v2
Comments
Maciej Los 21-Sep-15 15:47pm    
What exactly have you tried? What is input data?
Nested classes are classes where the class declaration is fully contained within the class declaration of another class. Your class definition does not look like nested class.

You're going to have to write recursion somewhere outside of the Linq query. Linq does not do recursion on its own.
Something like:
C#
public static List<Group> Flatten(List<Group> groups)
{
  List<Group> result = new List<Group>();
  if (groups != null)
  {
    // a bit of a hack to get around the self referential lambda issue.
    // (And, Yes, it must be two separate statements, and the null initialization is required)
    Action<Group> recurser = null;
    recurser = g => {
      if (g != null)
      {
        g.Groups.ForEach(recurser);
        result.Add(g);
      }
    };
    groups.ForEach(recurser);
  }
  return result;
}

(This is a pre-order, depth-first, traversal.)

Now you can:
C#
List<int> AllIds = Flatten(RootGroupList).Select(g => g.Id).ToList();


Edit:
An additional note: This doesn't check that the Groups property isn't null. A relatively safe way to ensure this would be to change:
C#
public class Group
{ 
    public int Id { get; set; }
    public string Description { get; set; }
    public int ParentId { get; set; }
    private readonly List<Group> _Groups = new List<Group>();
    public List<Group> Groups { get { return _Groups; } }
}

However, this prevents wholesale replacing the Groups property. Its contents must be manipulated with .Clear(), .Add(), etc.
 
Share this answer
 
v4
Comments
BillWoodruff 21-Sep-15 22:56pm    
+5 fascinating solution, Matt. First time I've seen this technique.
Matt T Heffron 22-Sep-15 12:26pm    
Thanks!
Here's an alternative approach that I use which doesn't require storing the entire hierarchy in a list, and isn't tied to a particular data structure:
C#
private static IEnumerable<TResult> SelectRecursiveIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TSource>> getChildren, Func<TSource, int, TResult> selector)
{
    var stack = new Stack<IEnumerator<TSource>>();

    try
    {
        stack.Push(source.GetEnumerator());
        while (0 != stack.Count)
        {
            var iter = stack.Peek();
            if (iter.MoveNext())
            {
                var current = iter.Current;
                yield return selector(current, stack.Count - 1);

                var children = getChildren(current);
                if (children != null) stack.Push(children.GetEnumerator());
            }
            else
            {
                iter.Dispose();
                stack.Pop();
            }
        }
    }
    finally
    {
        while (0 != stack.Count)
        {
            stack.Pop().Dispose();
        }
    }
}

public static IEnumerable<TResult> SelectRecursive<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TSource>> getChildren, Func<TSource, int, TResult> selector)
{
    if (source == null) throw new ArgumentNullException("source");
    if (selector == null) throw new ArgumentNullException("selector");
    if (getChildren == null) return source.Select(s => selector(s, 0));
    return SelectRecursiveIterator(source, getChildren, selector);
}

public static IEnumerable<T> SelectRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> getChildren)
{
    if (source == null) throw new ArgumentNullException("source");
    if (getChildren == null) return source;
    return SelectRecursiveIterator(source, getChildren, (s, _) => s);
}


To get the list of all IDs, you would then use something like this:
C#
List<int> allIds = groups.SelectRecursive(g => g.Groups).Select(g => g.Id).ToList();
 
Share this answer
 
Comments
Matt T Heffron 22-Sep-15 15:57pm    
Nice!
I wasn't willing to think that hard! :-)
+5

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900