0
     0
   /   \
  1     2
 / \   /|\
3   4 5 6 7

I'm trying to return the nodes without children (3,4,5,6,7) from an object using a recursive function.

It works using a global variable and this function:

def find(self, nodes):
    if not hasattr(self, 'children'): 
        nodes.append(self)
    else:
        for i in self.children:
            i.find(nodes)


nodes = []
node.find(nodes) # can be any node (0,1,2,3,4, etc.)
print(nodes)

But I would like to use return in my function. I tried something like this:

   def find2(self):
    if not hasattr(self, 'children'): 
        return self
    else:
        for i in self.children:
            return i.find2()


nodes = root.find2()
print(nodes)

But I get only 1 node. I also tried passing an array like on this post: PYTHON Return a list from a recursive function. But I don't get the result I want because tree structure (I guess)...

I'm stuck, can you help me? How to "return the result for each iteration of the recursive function to a variable"? Thank you

grrr
  • 151
  • 11

3 Answers3

1

Note that in your base case, you return one node. That node then get propagated back in the recursion, and eventually returned. I.e. the node you are returned is the first one without children you hit. There are multiple ways to fix this issue (e.g. by returning and concatenating lists), but I think a natural way to do this is to make your function a generator. To do this, simply replace the return by a yield. Then, the return value of the function functions as an iterator, with each iteration yielding the next value that would be "returned" in execution until the function terminates.

1

You've not provided enough code for me to make a runnable and testable example but here's my guess of what you seek:

def find(self):
    if not hasattr(self, 'children'):
        return [self]

    nodes = []

    for child in self.children:
        return nodes.extend(child.find())

    return nodes

# ...

nodes = root.find()
print(nodes)
cdlane
  • 40,441
  • 5
  • 32
  • 81
1

This is the input example:

     0
   /   \
  1     2
 / \   /|\
3   4 5 6 7

Think about what you want the (recursive) function to return for each of the nodes:

  • when called on the root (0), it should return the full result (3, 4, 5, 6, 7)
  • when called on a leaf node (e.g. 5), it should return that node (5)
  • for any other node (e.g. 1), it does as if that was a root node of a smaller tree

So, it sometimes returns one result and sometimes many. The error in your code is here, because the function ends on the first return, it does not return many:

    for i in self.children:
        return i.find2()

There are two solutions:

  • make a generator function, or
  • make a function which returns a list (make it return a list always, even if it has just one element!)

So, option 1:

def find(self):
    if not hasattr(self, 'children'): 
        yield self
    else:
        for child in self.children:
            yield from child.find()

option 2:

def find(self):
    if not hasattr(self, 'children'): 
        return [self]
    else:
        rtn = []
        for child in self.children:
            for result in child.find():
                rtn.append(result)
        return rtn

I prefer the generator. Additionally, I don't particularly like the fact that some of your nodes have a children attribute, while others do not. I would make sure all of them had children, which could be empty or non-empty. Then, the code becomes:

def find_leaves(self):
    if self.children:
        for child in self.children:
            yield from child.find_leaves()
    else:
        yield self
zvone
  • 18,045
  • 3
  • 49
  • 77
  • 1
    Thank you for this very nice answer. I didn't know about `yield` (and generators) nor `[self]`. I'm now going to read documentation about them to understand how they work. Ty it is working! – grrr May 21 '18 at 21:15
  • @cdlane `for result in child.find()` unpacks the list and then adds each item to `rtn`. I did not test it (i tested only `find_leaves`), but I think it should work... – zvone May 21 '18 at 21:50
  • You are correct, I did not see the nested loop. Though I think `.extend()` makes more sense than an entire extra loop! – cdlane May 21 '18 at 22:04
  • @cdlane Yes, extend would be better, but I thought this would be easier to understand... – zvone May 21 '18 at 22:16