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?