Select to view content in your preferred language

Recursion : Using the same function within itself

376
1
07-29-2024 07:24 PM
Labels (1)
DanPatterson
MVP Esteemed Contributor
3 1 376

I work with my own geometry class, arcpy's geometry classes and more recently, I foolishly looked at geojson data structures.  Nested objects are more common than you think, so I have time, so I messed around with recursive functions in python.

The get_keys function in my previous blog is a pure example of recursion, where the function calls itself.  

Sometimes a recursive action can call a function nested within a main functions.  Examples of both follow.

Let the nesting begin

Let's start with a simple example of a nested list which consists of nested lists, and simple lists of various sizes and depth.

 

    Notes
    -----
    Array type and depth/size::
          []         1
          [[]]       2, size 1
          [[], []]   2, size 2
          [[[]]]     3, size 1
          [[[], []]] 3, size 2

    Example
    -------
        >>> # input list            nested_len
        >>> lst = [                  idx0, idx1, count
        ...        [[1, 2, 3],      [[0, 0, 3],       two lists in list 0
        ...         [4, 5]],         [0, 1, 2],
        ...        [[1, 2]],         [1, 0, 2],       one list in list 1
        ...        [1, 2],           [2, 0], [2, 1],  no lists in list 2
        ...        [[]],             [3, 0, 0]        one list in list 3
        ...        [[1, 2, 3],       [4, 0, 3],       three lists in list 4
        ...         [4, 5],          [4, 1, 2],
        ...         [6, 7, 8]],      [4, 2, 3],
        ...        [[[1, 2, 3],      [5, 0, 2],       two lists in list 5
        ...          [4, 5]],
        ...         [[6, 7, 8]]]     [5, 1, 1]]
        ...       ]

 

Hope the above is self-explanatory.  Pay attention to the Notes section first, then try to follow the nested list object that contains other structures.

Ugly?  Not really, I just pulled replaced real-world coordinate pairs with simple integers. to illustrate.

Here is a function that pulls out information from a nested iterable, in this example `target_cls` will use python list objects as the example

def nested_len(obj, out=[], target_cls=list):
    """Return the lengths of nested iterables.

    Parameters
    ----------
    obj : iterable
        The iterable must be the same type as the `target_cls`.
    out : list
        The returned results in the form of a list of lists.
    target_cls : class
        A python, numpy class that is iterable.

    """

    def _len(obj):
        """Object length."""
        sub = len(obj) if isinstance(obj, target_cls) else 0
        return sub

    if not hasattr(obj, '__iter__'):
        print("\nIterable required (e.g. list/tuple")
        return None
    #
    for i, v0 in enumerate(obj):
        num = _len(v0)
        for j in range(0, num):
            k = v0[j]
            sub = [i, j]
            if isinstance(k, target_cls):
                s = _len(k)
                sub.append(s)
            out.append(sub)
    return out

 

Running that function using the Example input list data results in

nl = nested_len(lst, out=[], target_cls=list)
nl
[[0, 0, 3],
 [0, 1, 2],
 [1, 0, 2],
 [2, 0],
 [2, 1],
 [3, 0, 0],
 [4, 0, 3],
 [4, 1, 2],
 [4, 2, 3],
 [5, 0, 2],
 [5, 1, 1]]

Which is the other half of the explanation section.

What if you just want to know how deep a nested structure is.

# -- input
lst
Out[113]: 
[[[1, 2, 3], [4, 5]],
 [[1, 2]],
 [1, 2],
 [[]],
 [[1, 2, 3], [4, 5], [6, 7, 8]],
 [[[1, 2, 3], [4, 5]], [[6, 7, 8]]]]

# -- the function
def depth(lst):
    d = 0
    for item in lst:
        if isinstance(item, list):
            d = max(depth(item), d)
    return d + 1
    
# -- a call to it
depth(lst)
4
# -- four levels deep, max!!!

How about flattening a data structure and converting it to another one?  In this case, recursively flatten `lst` to numpy arrays.

def flatten(arr, list_=None):
    if list_ is None:
        list_ = []
    if isinstance(arr, list):
        for i in arr:
            if isinstance(i[0], list) and len(i) > 1:
                flatten(i, list_)
            else:
                list_.append(np.array(i))
    else:
        list_.append(np.array(arr))
    return list_

flatten(lst, list_=None)
 
[array([1, 2, 3]),
 array([4, 5]),
 array([[1, 2]]),
 array([1, 2]),
 array([], shape=(1, 0), dtype=float64),
 array([1, 2, 3]),
 array([4, 5]),
 array([6, 7, 8]),
 array([1, 2, 3]),
 array([4, 5]),
 array([[6, 7, 8]])]

Rather than grumbling about nested data structures, remember these few examples

 

 

1 Comment
About the Author
Retired Geomatics Instructor (also DanPatterson_Retired). Currently working on geometry projects (various) as they relate to GIS and spatial analysis. I use NumPy, python and kin and interface with ArcGIS Pro.
Labels