"""
some recursive functions on nested lists
"""

from typing import List, Any

def depth(obj):
    """
    Return 0 if obj is a non-list, or 1 + maximum
    depth of elements of obj, a possibly nested
    list of objects.

    Assume obj has finite nesting depth

    @param list[object]|object obj: possibly nested list of objects
    @rtype: int

    >>> depth(3)
    0
    >>> depth([])
    1
    >>> depth([[], [[]]])
    3
    >>> depth([1, 2, 3])
    1
    >>> depth([1, [2, 3], 4])
    2
    """
    if obj == []:
        return 1
    elif not isinstance(obj, list):
        return 0
    else:
        return 1 + max([depth(i) for i in obj])


def rec_max(obj):
    """
    Return obj if it's an int, or the maximum int in obj,
    a possibly nested list of numbers.

    Assume: obj is an int or non-empty list with finite nesting depth,
    and obj doesn't contain any empty lists

    @param int|list[int|list[...]] obj: possibly nested list of int
    @rtype: int

    >>> rec_max([17, 21, 0])
    21
    >>> rec_max([17, [21, 24], 0])
    24
    >>> rec_max(31)
    31
    """

    if not isinstance(obj, list):
        return obj
    else:
        return max([rec_max(i) for i in obj])


def flatten(S):
    """
    Flatten a list of lists to a list of depth 1
    @param list[list] S: The list of lists
    @rtype: list

    >>> flatten([1, 2, [3, 4, [5]]])
    [1, 2, 3, 4, 5]
    """

    if not isinstance(S, list):
        return [S]
    else:
        return sum([flatten(i) for i in S],[])



def concat_strings(string_list):
    """
    Concatenate all the strings in possibly-nested string_list.

    @param list[str]|str string_list:
    @rtype: str

    >>> concat_strings("brown")
    'brown'
    >>> concat_strings(["now", "brown"])
    'nowbrown'
    >>> concat_strings(["how", ["now", "brown"], "cow"])
    'hownowbrowncow'
    """
    if not isinstance(string_list, list):
        return str(string_list)
    else:
        return "".join([concat_strings(i) for i in string_list])


def nested_count(obj):
    """
    Return the number of non-list elements of list_ or its nested sub-lists.

    @param list obj: possibly nested list to count elements of
    @rtype: int

    >>> list_ = ["how", ["now", "brown"], "cow"]
    >>> nested_count(list_)
    4
    >>> list_=[]
    >>> nested_count(list_)
    0
    >>> nested_count([3])
    1
    """

    if not isinstance(obj, list):
        return 1
    else:
        return sum([nested_count(i) for i in obj])


def list_level(obj:List[Any], d:int) -> List:
    """

    Return the non-list elements at a particular level.

    @param list obj: possibly nested list
    @param int d: The level to print out
    @rtype: List

    >>> list_ = [1, [2, [3, 4], 5], 2]
    >>> list_level(list_, 2)
    [2, 5]
    >>> list_level(list_, 3)
    [3, 4]

    """
    if d < 1:
        return []
    elif d == 1:
        return sum ([[i] if not isinstance(i, list) else
                     [] for i in obj], [])
    else:
        return sum([[] if not isinstance(i, list) else
                    list_level(i, d-1) for i in obj], [])


def list_levels(obj:List[Any]) -> List:
    """

        Return the non-list elements at all levels as a list.

        @param list obj: possibly nested list
        @rtype: List

        >>> list_ = [1, [2, [3, 4], 5], 2]
        >>> list_levels(list_)
        [[1, 2], [2, 5], [3, 4]]

    """
    return [list_level(obj, i) for i in range(1, depth(obj)+1)]




if __name__ == '__main__':
    import doctest
    doctest.testmod()