Casey Casey - 2 months ago 12
Python Question

Loop through categories recursively

I'm building a python function which returns an array to be used in a select dropdown field. I have tried two versions so far.

Both of them work, with the first returning a properly formatted select field. However, the first solution only goes two levels deep. I intend to add more depth to the categories.

My second example is my attempt at doing this recursively to support more levels. It works but I'm wondering how I can optimize it and add dashes similar to the first example.

# first example two levels deep, formatted properly with dashes
def build_choice_tree():
categories = Category.query.get(1).children
items = [(1, 'None')]
for root in categories:
items.append((root.id, root.name))
if root.children:
for subcat1 in root.children:
items.append((subcat1.id, '- ' + subcat1.name))
if subcat1.children:
for subcat2 in subcat1.children:
items.append((subcat2.id, '--' + subcat2.name))
return items

# second example goes multiple levels, needs dashes
def build_choice_tree2():
categories = Category.query.get(1).children
items = []

def loop(categories):
for category in categories:
items.append((category.id, category.name))
if category.children:
loop(category.children)
return items
result = loop(categories)
return result

Answer

Use a counter to store the number of dashes you want to add and multiply the dashes by that count. Also to make a function truly recursive you need to add the return statement.

def build_choice_tree2():
    categories = Category.query.get(1).children
    items = []
    count = 1

    def loop(categories, count):
        for category in categories:
            items.append((category.id,'-' * count, category.name))
            if category.children:
                count +=1
                return loop(category.children, count)
        return items

    return loop(categories, count)

Personally i would separate loop into a different method like this and avoid the inner loop method in build_choice_tree2. i'll also make items a default argument. since default arguments (mutable) are evaluated at function definition time, it will never get reset to it's original value of an empty list.

def loop(categories, count=1, items=[]):
    for category in categories:
        items.append((category.id,'-' * count, category.name))
        if category.children:
            count +=1
            return loop(category.children, count)
    return items