Search code examples
c#.netgenericsrecursionicollection

How to recursively traverse nested generic collection?


I want to count the number of a class in a nested collection, e.g. [[[1,2],[3,4,5]],[[1,2],[3,4,5]]]. Here the referred class is "int" and the expected answer is 10.

I generate this list as:

        List<int> list1 = new List<int>(2);
        list1.Add(1);
        list1.Add(2);
        List<int> list2 = new List<int>(3);
        list2.Add(3);
        list2.Add(4);
        list2.Add(5);

        List<List<int>> listlist = new List<List<int>>(2);
        listlist.Add(list1);
        listlist.Add(list2);

        List<List<List<int>>> listlistlist = new List<List<List<int>>>(2);
        listlistlist.Add(listlist);
        listlistlist.Add(listlist);

When I am programing, I prefer to write such a method for a generic class, my codes are:

    public static int CountT<T>(ICollection<T> pTCol, int intLevel = 1)
    {
        int intCount = 0;
        if (intLevel > 0)
        {
            intLevel--;
            foreach (T pT in pTCol)
            {
                ICollection<T> subTCol = pT as ICollection<T>;   //PROBLEM: I got "null" for subTCol when the program was running
                intCount += CountT(subTCol, intLevel);
            }
        }
        else if (intLevel == 0)
        {
            intCount = pTCol.Count;
        }
        return intCount;
    }

I tested the above code by

        int intCount = CountT(listlistlist, 2);

then I got the problem

        ICollection<T> subTCol = pT as ICollection<T>;   //PROBLEM: I got "null" for subTCol when the program was running

I also tried the codes:

    public static int CountT2<T, T2>(ICollection<T> pTCol, int intLevel = 1)
    {
        int intCount = 0;
        if (intLevel > 0)
        {
            intLevel--;
            foreach (T pT in pTCol)
            {
                ICollection<T2> subTCol = pT as ICollection<T2>;
                intCount += CountT2(subTCol, intLevel);   //PROBLEM: The type arguments for method cannot be inferred from the usage. Try specifying the type arguments explicitly. (I could not pass the compiling)
            }
        }
        else if (intLevel == 0)
        {
            intCount = pTCol.Count;
        }
        return intCount;
    }

I could not pass the compiling

        intCount += CountT2(subTCol, intLevel);   //PROBLEM: The type arguments for method cannot be inferred from the usage. Try specifying the type arguments explicitly. (I could not pass the compiling)

How can I do it?


Solution

  • ICollection subTCol = pT as ICollection //PROBLEM: I got "null" for subTCol

    The reason this is null is pT is not an ICollection<T> because T is int and listlistlist is a List<List<List<T>>>. Therefore pT is a List<List<T>> which is why if you try to cast it to ICollection<T> you'll get null.

    I want to count the number of items in a nested collection.

    You can do this much easier using 's Sum method. If you know the nesting level, for example:

    Assert.AreEqual(10, listlistlist.Sum(x => x.Sum(y => y.Count)));
    

    If you don't know the nesting level or you want a more generic method, you could create an extension method like:

    public static class RecursiveSumExtension
    {
        public static int RecursiveSum(this IEnumerable items)
        {
            if (null == items)
                return 0;
    
            var total = 0;
    
            foreach (var item in items)
            {
                if (item is IEnumerable)
                    total += (item as IEnumerable).RecursiveSum();
    
                else
                    total++;
            }
    
            return total;
        }
    }
    

    and the test:

     Assert.AreEqual(10, listlistlist.RecursiveSum());
    

    List Generation

    As a quick aside, you can use 's collection initializer syntax to make the code a bit more readable when you are creating collections:

            var listlist = new List<List<int>>
            {
                new List<int> {1, 2},
                new List<int> {3, 4, 5}
            };
    
            var listlistlist = new List<List<List<int>>>
            {
                listlist,
                listlist
            };