简体   繁体   中英

Function that applies to lists of arbitrary depth

How do you write a function that can apply to lists of arbitrary depth? I'll give two examples of the type of function I'm trying to write in C#.

Example 1: a depth() method that can be used on any list:

int depth<T>(this List<T> x) {
    if (/*x[0] is a List<of something>*/) { return x[0].depth + 1; }
    else { return 1; }
}

Example 2: an implode() method that can be used on a list of strings at any depth:

List<List<string>> first = /* ("a","b"),("x","y","z") */
List<string> second = /* "a","b","c" */
first.implode() /* returns a string: "(a,b),(x,y,z)" */
second.implode() /* returns a string: "a,b,c" */

But I can't figure out how to make such a function that applies to lists of arbitrary depth in C#.

You have two questions here; try to ask only one question per posting. You can compute your Depth function like this:

static int Depth(Type type)
{
    int depth = 0;
    for(
        Type current = type; 
        current.IsGenericType && current.GetGenericTypeDefinition() == typeof(List<>) ;
        current = current.GetGenericArguments()[0] )
    {
        depth += 1;
    }
    return depth;
}

static int Depth<T>(this List<T> x) 
// x is unused; you could eliminate it entirely. 
{
    return Depth(typeof(List<T>));
}

You could easily make a general purpose method to descend the sets (or sets of sets). I've included example Depth and Implode methods. Also note that the parameter to the Depth method is not used (also mentioned by Eric in his answer). You could easily omit the parameter from the signature of the Depth method and pass default(List<TElement>) to the Descend method.

/// <summary>
/// Extension methods for lists (or lists of lists)
/// </summary>
public static class ListExtensions
{
    /// <summary>
    /// Cache the generic method definition for <see cref="Descend{TElement, TResult}"/>
    /// </summary>
    private static readonly MethodInfo DescendMethod = new Func<List<object>, Func<object, object>, Func<IEnumerable<object>,object>, object>(Descend).Method.GetGenericMethodDefinition();

    /// <summary>
    /// Descends a potentially nested set of enumerables
    /// </summary>
    /// <typeparam name="TElement">The current enumerable type</typeparam>
    /// <typeparam name="TResult">The type of the result of the calling method</typeparam>
    /// <param name="set">The set of elements to work on</param>
    /// <param name="compute">The computation to perform on a single element</param>
    /// <param name="aggregate">The computation to perform on a collection of results from computations on single elements</param>
    /// <returns>The result of the aggregation of all the results from all levels of the set</returns>
    private static TResult Descend<TElement, TResult>(this List<TElement> set, Func<object, TResult> compute, Func<IEnumerable<TResult>, TResult> aggregate)
    {
        var elementType = typeof(TElement);

        if (elementType.IsGenericType && typeof(List<>) == elementType.GetGenericTypeDefinition())
        {
            var method = DescendMethod.MakeGenericMethod(elementType.GetGenericArguments()[0], typeof(TResult));

            if (ReferenceEquals(set, null))
            {
                return aggregate(new[] { (TResult)method.Invoke(null, new object[] { default(TElement), compute, aggregate }) });
            }

            var results = set.Select(item => (TResult)method.Invoke(null, new object[] { item, compute, aggregate })).ToList();

            if (results.Count == 0)
            {
                return aggregate(new[] { (TResult)method.Invoke(null, new object[] { default(TElement), compute, aggregate }) });
            }

            return aggregate(results);
        }

        return aggregate((set ?? new List<TElement>()).OfType<object>().Select(compute));
    }

    /// <summary>
    /// Walks down a set (possibly of sets) to determine its depth
    /// </summary>
    /// <typeparam name="TElement">The type of elements found in the top level set</typeparam>
    /// <param name="set">The set to walk down</param>
    /// <returns>The depth of the set</returns>
    public static int Depth<TElement>(this List<TElement> set)
    {
        return set.Descend(x => 0, x => x.FirstOrDefault() + 1);
    }


    /// <summary>
    /// Walks down a set (possibly of sets) to determine its depth
    /// </summary>
    /// <typeparam name="TElement">The type of elements found in the top level set</typeparam>
    /// <returns>The depth of the set</returns>
    public static int Depth<TElement>()
    {
        return Descend(default(List<TElement>), x => 0, x => x.FirstOrDefault() + 1);
    }

    /// <summary>
    /// Creates a string representation of a set (possibly of sets)
    /// </summary>
    /// <typeparam name="TElement">The type of elements found in the top level set</typeparam>
    /// <param name="set">The set to create the string representation of</param>
    /// <returns>A string representation of the set</returns>
    public static string Implode<TElement>(this List<TElement> set)
    {
        var result = set.Descend(x => x != null
                                          ? x.ToString()
                                          : null,
                                 x =>
                                 {
                                     var elements = x.Where(y => !ReferenceEquals(y, null)).ToList();

                                     if (elements.Count == 0)
                                     {
                                         return null;
                                     }

                                     return "(" + String.Join(",", elements) + ")";
                                 });

        if (result != null && result.Length > 2)
        {
            return result.Substring(1, result.Length - 2);
        }

        return result;
    }
}

class Program
{
    private static void Main()
    {
        var tmp2 = new List<List<List<List<string>>>>();
        var tmp = new List<List<List<string>>>
            {
                new List<List<string>>
                {
                    new List<string>
                    {
                        "1",
                        "2",
                        "3"
                    },
                    new List<string>
                    {
                        "4"
                    }
                },
                new List<List<string>>
                {
                    new List<string>
                    {
                        "5",
                        "6",
                        "7"
                    }
                }
            };

        Console.WriteLine(ListExtensions.Depth<List<List<List<List<string>>>>>());
        Console.WriteLine(tmp2.Depth());
        Console.WriteLine(tmp2.Implode());

        Console.WriteLine(tmp.Depth());
        Console.WriteLine(tmp.Implode());

        Console.ReadLine();
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM