Use python nonlocal varibles to store the output of a recursion VS return the recursion directly

76 Views Asked by At

I'm trying to design a function that produce the names of wizards who be in the same house as their parent, by passing in some Wizard that come together formed a family tree, as stated in the docstrings.

## Data Definitions:
## Wizard is (name, house, children)
## - name(str) is name of the wizard.
## - house(str) is name of the house the wizard in.
## - children(listof str) is a list of the wizard's children.
wa = ('A', 'S', [])
wb = ('B', 'G', [])
wc = ('C', 'S', [])
wd = ('D', 'H', [])
we = ('E', 'R', [wb])
wf = ('F', 'G', [we, wa])
wg = ('G', 'S', [wc, wd])
wh = ('H', 'G', [wg, wf])

These are some examples of datas to be passed to the function.


def get_same_house(wiz):
    """Produce the names of wizards who be in the same house as their parent.

    Args:
        wiz(tuple[str, str, list]): name, house and list of children of the wizard.

    Returns:
        list: names of wizards
    """
    result = []
    def fn_for_wiz(wiz, house):
        if wiz[1] == house:
            nonlocal result
            result.append(wiz[0])
        else:
            house = wiz[1]
        fn_for_low(wiz[2], house)

    def fn_for_low(low, house):
        if not low:
            return None
        fn_for_wiz(low[0], house), fn_for_low(low[1:], house)

    fn_for_wiz(wiz, '')
    return result

In the above function I use a variable in nonlocal scope result to store wanted values and make the recursion helpers return nothing.


def get_same_house_as_parent(wiz):
    """Produce names of wizard which be in the same house as their parent.
    
    Args:
        wiz(tuple[str, str, list]): name, house and children of a wizard.

    Returns:
        list: names of the wizards

    todo: list of wizard to traverse next contains (wizard, parent name)
    """
    result = []
    def fn_for_wiz(wiz, ph, todo):
        if wiz[1] == ph:
            nonlocal result
            result.append(wiz[0])
        todo.extend([(w, wiz[1]) for w in wiz[2]])
        return fn_for_low(todo)

    def fn_for_low(todo):
        if not todo:
            return result
        return fn_for_wiz(todo[0][0], todo[0][1], todo[1:])

    return fn_for_wiz(wiz, '', [])

This one I use the combination of helper-functions' return and nonlocal variable result.


def get_same_house_nostoring(wiz):
    """Produce the names of wizards who be in the same house as their parent.

    Args:
        wiz(tuple[str, str, list]): name, house and list of children of the wizard.

    Returns:
        list: names of wizards
    """
    def fn_for_wiz(wiz, house):
        if wiz[1] == house:
            return [wiz[0]] + fn_for_low(wiz[2], house)
        else:
            house = wiz[1]
            return fn_for_low(wiz[2], house)

    def fn_for_low(low, house):
        if not low:
            return []
        return fn_for_wiz(low[0], house) + fn_for_low(low[1:], house)

    return fn_for_wiz(wiz, '')

For this function I use the return statement to return wanted values directly.


So I'm wondering which one I should use. I can wrap up the third fn using accumulators to form a tail recursion for better performance. But still, for me, the first and second functions' methods are a lot less complex and easier to understand or debug, making them better to use especially when a designing function get larger and more complicate. But are there any conventions for this? What're the difference between them?

Additional question: Can functions using the same method as the first fn be considered as tail recursion?

0

There are 0 best solutions below