You can do a depth first search. Just keep track of which items you've seen in a set. Then yield each one you haven't seen and yield from the result of recursive call:

def get_all_descendants(t, d, seen=None):
    if seen is None:
        seen = set([t])
    for item in d[t]:
        if item not in seen:
            seen.add(item)
            yield item
            yield from get_all_descendants(item, d, seen)

list(get_all_descendants('charges', d))

This will give you this list:

['mview',
 'status',
 'accounts',
 'nodate',
 'tainlog',
 'remfile',
 'retlog',
 'retfile',
 'bb',
 'payfile',
 'aa',
 'snaps',
 'raw',
 'pview',
 'balance']
Answer from Mark on Stack Overflow
Top answer
1 of 2
2

You can do a depth first search. Just keep track of which items you've seen in a set. Then yield each one you haven't seen and yield from the result of recursive call:

def get_all_descendants(t, d, seen=None):
    if seen is None:
        seen = set([t])
    for item in d[t]:
        if item not in seen:
            seen.add(item)
            yield item
            yield from get_all_descendants(item, d, seen)

list(get_all_descendants('charges', d))

This will give you this list:

['mview',
 'status',
 'accounts',
 'nodate',
 'tainlog',
 'remfile',
 'retlog',
 'retfile',
 'bb',
 'payfile',
 'aa',
 'snaps',
 'raw',
 'pview',
 'balance']
2 of 2
2

I think @Mark's answer can be improved. Creating an inner loop means we will not check if seen is a set on each iteration. And moving the if outside of the for loop means you can skip many unnecessary checks on child nodes when the parent node has already been seen. The loop also uses the closure property so d and seen do not need to be passed to every recursive call -

def dfs(t, d):
  seen = set()                    # unconditional
  def loop(t):                    # single parameter
    if t not in seen:             # if outside of loop
      seen.add(t)
      yield t
      for item in d[t]:
        yield from loop(item)
  return drop(loop(t), 1)         # exclude starting node from result

This depends on a generic drop helper which removes n items from an iterable -

def drop(it, n):
  for _ in range(n):
    next(it, None)
  return it

The output is identical -

for node in dfs("charges", d):
  print(node)
mview
status
accounts
nodate
tainlog
remfile
retlog
retfile
bb
payfile
aa
snaps
raw
pview
balance
Top answer
1 of 2
1

The data preparation step is a little hideous, because your IDs are starting from one we need a blocker element None at position 0:

original = [(1, 'A', 0),
            (2, 'B', 1),
            (3, 'C', 1),
            (4, 'D', 2),
            (5, 'E', 2),
            (6, 'F', 3),
            (7, 'G', 2),
            (8, 'H', 6),
            (9, 'I', 4)]
ids,data,parents = zip(*original)
data =[None]+list(data)
parents = [None]+list(parents)

This code will work once you get your input data in the right format:

ids = range(1,10)
parents = [None,0,1,1,2,2,3,2,6,4]
data =[None,"A","B","C","D","E","F","G","H","I"]

def get_parents(node):
    if not parents[node]:
        return data[node]
    return get_parents(parents[node])+data[node]
for id in ids:
    print ".".join(list(get_parents(id)))
>>> 
A
A.B
A.C
A.B.D
A.B.E
A.C.F
A.B.G
A.C.F.H
A.B.D.I
2 of 2
0

After a writing out each step on a piece of paper, I was able to come up with the following:

def generate_category_strings(cat_list, parent_item, sent_str, retlist=[]):
    """
    Concatenates categories together and returns a list of them.
    """
    if parent_item is None:
        parent_item = cat_list[0]
        retlist.append(parent_item[1])

    parent_id = parent_item[0]
    parent_name = parent_item[1]

    if sent_str is None:
        sent_str = parent_name

    children = [_x for _x in cat_list if _x[2] == parent_id]

    if len(children) == 0:
        # base case, no children
        return ["{}.{}".format(sent_str, parent_name)]
    else:
        for child in children:
            str_to_send = "{}.{}".format(sent_str, child[1])
            retlist.append(str_to_send)
            generate_category_strings(cat_list, child, str_to_send, retlist)
        return retlist

Running this on the original dataset:

data = [(1, "A", 0), (2, "B", 1), (3, "C", 1), (4, "D", 2),
        (5, "E", 2), (6, "F", 3), (7, "G", 2), (8, "H", 6),
        (9, "I", 4),
       ]

a = generate_category_strings(data, None, None)
for item in a:
    print(item)

prints:

A
A.B
A.B.D
A.B.D.I
A.B.E
A.B.G
A.C
A.C.F
A.C.F.H
🌐
Python
mail.python.org › pipermail › tutor › 2013-June › 096180.html
[Tutor] How to find descendants recursively?
June 17, 2013 - Avoiding these troubles is not ... we want to define a function, with a name, that will call itself. Each time you descend into a child node, you call the function recursively to process that childlist....
🌐
Stack Overflow
stackoverflow.com › questions › 63922191 › how-do-i-get-a-parent-node-through-recursion-in-python
How do I get a parent node through recursion in python? - Stack Overflow
class node(): def __init__(self, name, parent=None): self.name = name self.parent: node = parent def get_bottom_up_ancestors(self): if self.parent: return [self.name] + self.parent.get_bottom_up_ancestors() return [self.name] def get_top_down_ancestors(self): return self.get_bottom_up_ancestors()[::-1] root = node("Top") child1 = node("first", parent=root) child2 = node("second", parent=root) grandchild1 = node("grandchild", parent=child1) print(grandchild1.get_bottom_up_ancestors()) print(grandchild1.get_top_down_ancestors()) ... Sign up to request clarification or add additional context in comments. ... Yes! It is something like this! But is there any way to recursively add the inputs("Top", "first","second","grandchild").
🌐
Simonhessner
simonhessner.de › python-3-recursively-print-structured-tree-including-hierarchy-markers-using-depth-first-search
Python 3: Recursively print structured tree including hierarchy markers using depth-first search – Simon's blog
My algorithm uses a depth-first search approach and keeps records about which markers to print on each level when recursing further down by passing a boolean array. This array is constructed based on the information if a node is the last child of its parent or not.
Top answer
1 of 3
4

This implementation should work

def get_family_tree(person):
    """ return a family tree for a Person object """

    children = person.children.all()

    if not children:
        # this person has no children, recursion ends here
        return {'name': person.name, 'children': []}

    # this person has children, get every child's family tree
    return {
        'name': person.name,
        'children': [get_family_tree(child) for child in children],
    }

Note that this will take as many database calls as there are persons. You can try to fetch all the data into memory if you run into performance issues.

Thinking About Recursion

One way to think about recursion is to start off with the base case - i.e. where the recursion will end. In your case, we know how the family tree looks like if a person has no children:

{
    'name': 'FirstPerson',
    'children': [],
}

After you have the base case(s), think about the problem where you have to perform the recursion once.

In your case, it would be parents with children, but no grand children. We know how each child's family tree should look - it's just the base case! This leads us to the solution where we return the parent's name, and a list of each child's family tree. Leading to something like:

{
    'name': FirstPerson,
    'children': [<each element is a child's family tree>]
}

Edit

Django automatically generates reverse relations for a ForeignKey.

class Person(models.Model):
    ....
    parent = models.ForeignKey('self', related_name='children', blank=True, null=True)

p = Person()
p.children.all() # automatically fetch all Person objects where parent=p

See https://docs.djangoproject.com/en/1.9/ref/models/fields/#foreignkey

2 of 3
1

You should try the package django-mptt as it works great or this purpose:

You can use TreeForeignKey() as the ForeignKey.

You can then add this method to the model to get the objects (or look into the docs I have provided to get the children instead of the parents/ancestors):

def get_category_and_parents(self):
    """ Recursively retrieves parent categories (including self) using MPTT """
    full_category = self.category.get_ancestors(include_self=True)
    return full_category

Github:

https://github.com/django-mptt/django-mptt

Docs:

http://django-mptt.github.io/django-mptt/mptt.models.html#mptt.models.MPTTModel.get_children

Find elsewhere
Top answer
1 of 4
5

Here is a pure XSLT solution -- efficiently using keys (equivalent to hash-tables) and just 23 lines -- the shortest solution so far.

This is also computationally the simplest one -- compare nesting level 1 to nesting level of 4 - 5 ...

This solution is tail-recursive meaning that any good XSLT processor optimizes it with iteration, thus avoiding the possibility of stack-overflow, as the maximum call-stack depth remains constant (1).

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:key name="kNodeByChild" match="node" use="@child"/>
 <xsl:key name="kNodeByName" match="node" use="@name"/>

  <xsl:template match="/*">
    <xsl:apply-templates select="node[not(key('kNodeByChild', @name))]"/>
  </xsl:template>

  <xsl:template match="node[not(key('kNodeByName', @child))]">
    <xsl:param name="pParentPath"/>
    <xsl:value-of select="concat($pParentPath, @name, ' ---> ', @child, '&#xA;')"/>
  </xsl:template>

  <xsl:template match="node">
    <xsl:param name="pParentPath"/>

    <xsl:apply-templates select="key('kNodeByName', @child)">
      <xsl:with-param name="pParentPath" select="concat($pParentPath, @name, ' ---> ')"/>
    </xsl:apply-templates>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<nodes>
    <node name="Car" child="Engine"/>
    <node name="Car" child="Wheel"/>
    <node name="Engine" child="Piston"/>
    <node name="Engine" child="Carb"/>
    <node name="Carb" child="Bolt"/>
    <node name="Spare Wheel"/>
    <node name="Bolt" child="Thread"/>
    <node name="Carb" child="Foat"/>
    <node name="Truck" child="Engine"/>
    <node name="Engine" child="Bolt"/>
    <node name="Wheel" child="Hubcap"/>
</nodes>

The wanted, correct result is produced:

Car ---> Engine ---> Piston
Car ---> Engine ---> Carb ---> Bolt ---> Thread
Car ---> Engine ---> Carb ---> Foat
Car ---> Engine ---> Bolt ---> Thread
Car ---> Wheel ---> Hubcap
Spare Wheel ---> 
Truck ---> Engine ---> Piston
Truck ---> Engine ---> Carb ---> Bolt ---> Thread
Truck ---> Engine ---> Carb ---> Foat
Truck ---> Engine ---> Bolt ---> Thread
2 of 4
5

It has been a long time since I did anything with graphs but this should be pretty close if not the most optimal approach:

x = """<?xml version="1.0"?>
<nodes>
    <node name="Car" child="Engine"></node>
    <node name="Engine" child="Piston"></node>
    <node name="Engine" child="Carb"></node>
    <node name="Car" child="Wheel"></node>
    <node name="Wheel" child="Hubcaps"></node>
    <node name="Truck" child="Engine"></node>
    <node name="Truck" child="Loading Bin"></node>
    <nested>
        <node name="Spare Wheel" child="Engine"></node>
    </nested>
    <node name="Spare Wheel" child=""></node>

</nodes>"""

from lxml import etree

xml = etree.fromstring(x)
graph = {}
nodes = set()
for x in xml.xpath("//node"):
    par, child = x.xpath(".//@name")[0], x.xpath(".//@child")[0]
    graph.setdefault(par, set())
    graph[par].add(child)
    nodes.update([child, par])


def find_all_paths(graph, start, end, path=None):
    if path is None:
        path = []
    path = path + [start]
    if start == end:
        yield path
    for node in graph.get(start, []):
        if node not in path:
            for new_path in find_all_paths(graph, node, end, path):
                yield new_path


for n in graph:
    for e in nodes:
        if n != e:
            for path in find_all_paths(graph, n, e):
                if path:
                    print("--> ".join(path))

That would give you:

Engine--> Carb
Engine--> Piston
Car--> Engine
Car--> Wheel
Car--> Wheel--> Hubcaps
Car--> Engine--> Carb
Car--> Engine--> Piston
Spare Wheel--> Engine
Spare Wheel--> 
Spare Wheel--> Engine--> Carb
Spare Wheel--> Engine--> Piston
Wheel--> Hubcaps
Truck--> Engine
Truck--> Engine--> Carb
Truck--> Engine--> Piston
Truck--> Loading Bin
Top answer
1 of 2
12

I think it might be more helpful to you if I post a working example of how to do this, as opposed to going through where you code is having problems. We might get to the point of understanding a lot faster that way. Your code has the correct idea that it needs to track the depth as it goes. But the only thing it is missing is a sense of nested depth (tree). It only knows the previous node_count, and then its current child count.

My example uses a closure to start the depth tracking object, and then creates an inner function to do the recursive part.

def recurse(box):

    boxes = not isinstance(box, (list, tuple)) and [box] or box

    depth = [1]

    def wrapped(box):

        depthStr = '.'.join([str(i) for i in depth])
        print "%s %s" % (depthStr, box.name)

        depth.append(1)
        for child in box.boxItems:
            wrapped(child)
            depth[-1] += 1
        depth.pop()

    for box in boxes:
        wrapped(box)
        depth[0] += 1

Sample output from your examples:

>>> recurse(example)
1 Example Box
1.1 Big Box
1.1.1 Normal Box
1.1.2 Friendly Box
1.2 Cool Box

>>> recurse([example, example])
1 Example Box
1.1 Big Box
1.1.1 Normal Box
1.1.2 Friendly Box
1.2 Cool Box
2 Example Box
2.1 Big Box
2.1.1 Normal Box
2.1.2 Friendly Box
2.2 Cool Box

Breaking this down:

We first accept a box argument, and automatically convert it locally to a list, if you had only passed in a single box item. That way you can pass either one box objects, or a list/tuple of them.

depth is our depth tracker. Its a list of ints that we will build up and shrink down as the recursion happens. It starts at 1 for the first item / first level. Over time it can look like this: [1,1,2,3,1] depending on how deep it traverses. This is the major difference between my code and yours. Each recursion has access to this state.

Now we have this inner wrapped function. Its going to take a current box item and print it, and then iterate over its children. We get our print string by joining the current depth list, and then the name.

Every time we drop down into a child list, we add a starting level 1 to our depth list, and when we come out of that child loop, we pop it back off again. For every child in that loop, we increment that last item up.

Outside of that wrapped inner function, we then start the whole thing by looping over our initial boxes, calling wrapped and then incrementing our first level.

The inner wrapped function uses the depth list in a closure. I am willing to bet that others can offer some further improvements on this, but its what I came up with for an example.

Note about the args for the function

We could have also designed recurse to instead take a variable length argument list, instead of checking for a list. It would look like this (and would get rid of that first boxes = check):

def recurse(*boxes):
    #boxes will always come in as a tuple no matter what

>>> recurse(example)
>>> recurse(example, example, example)

And if you originally start with a list of box items, you can pass it by doing:

>>> boxes = [example, example, example]
>>> recurse(*example)    # this will unpack your list into args
2 of 2
1

You have two options:

  1. Keep track of the additional information as an additional parameter in your recursion, e.g. myRecursiveFunction(..., ancestry=[])
  2. Have each BoxItem keep track of its parent, whenever it is embedded in a BoxItem (in the __init__ constructor, set child.parent = self for each child). This is bad if you plan to have a BoxItem in more than one box.
Top answer
1 of 2
5

You can use networkx to solve the problem. Note if you use networkx you don't need the highest columns. The main function to find all paths is all_simple_paths

# Python env: pip install networkx
# Anaconda env: conda install networkx
import networkx as nx

# Create network from your dataframe
#G = nx.from_pandas_edgelist(df, source='parent', target='child',
#                            create_using=nx.DiGraph)

# For older versions of networkx
G = nx.DiGraph()
for _, (source, target) in df[['parent', 'child']].iterrows():
    G.add_edge(source, target)

# Find roots of your graph (a root is a node with no input)
roots = [node for node, degree in G.in_degree() if degree == 0]

# Find leaves of your graph (a leaf is a node with no output)
leaves = [node for node, degree in G.out_degree() if degree == 0]

# Find all paths
paths = []
for root in roots:
  for leaf in leaves:
    for path in nx.all_simple_paths(G, root, leaf):
        paths.append(path)

# Create a new dataframe
out = pd.DataFrame(paths).fillna('')
out.columns = reversed(out.add_prefix('level ').columns)

Output:

>>> out
  level 3 level 2 level 1 level 0
0       a       b       c        
1       a       b       d       e
2 of 2
2

This can be done using the networkx library. You do not need the highest column. It can be deduced from the graph.

import pandas as pd
import networkx as nx
df = pd.DataFrame([ \
        ('a',   'b',    1), \
        ('b',   'c',    0), \
        ('b',   'd',    0), \
        ('d',   'e',    0)], columns=['parent', 'child', 'highest'])

parents = df.parent.values
childs = df.child

G = nx.DiGraph()
for i in range(len(parents)):
    parent = parents[i]
    child = childs[i]

    if not parent in G.nodes:
        G.add_node(parent)

    if not child in G.nodes:
        G.add_node(child)

    G.add_edge(parent, child)

roots = [node for node in G.nodes if G.in_degree(node) == 0]
root = roots[0]

leaves = [node for node in G.nodes if G.out_degree(node) == 0]

all_paths = [list(nx.simple_paths.all_simple_paths(G, root, leaf)) for leaf in leaves]

# Flatten
all_paths = [tuple(path) for paths in all_paths for path in paths]

paths_df = pd.DataFrame(all_paths) 

I will leave for you to rename the columns as per your liking.

🌐
Codecademy
codecademy.com › learn › learn-recursion-python › modules › recursion-python › cheatsheet
Learn Recursion with Python: Recursion: Python Cheatsheet | Codecademy
RECURSIVE STEP: 1. Find the middle index of the list. 2. Create a tree node with the value of the middle index. 3. Assign the tree node's left child to a recursive call with the left half of list as input. 4. Assign the tree node's right child to a recursive call with the right half of list as input.
Top answer
1 of 2
3

More Python thing than Blender's one, but you can:

import bpy

def find_meshes_recursive( root, levels=10, meshes=None ):
    # Initialize the result once
    if meshes is None:
        meshes = []

    def recurse( parent, result, level, levels ):
        # Does nothing if level is reached
        if level < levels:
            # Keeps meshes
            if parent.type == 'MESH':
                result.append(parent)
            # Look over children at next level
            for child in parent.children:
                recurse( child, result, level + 1, levels )
    
    recurse( root, meshes, 0, levels )
    return meshes

root = bpy.context.object

meshes = find_meshes_recursive(root, levels = 10)
print(meshes)

Note that you can also do it in non recursive way:

def find_meshes(root, levels=10, meshes=None):
    meshes = [] if meshes is None else meshes
    
    parents = [root]
    while levels > 0 and parents:
        meshes.extend([obj for obj in parents if obj.type == 'MESH'])
        parents = [child for obj in parents for child in obj.children]
        levels -= 1
        
    return meshes
2 of 2
4

Recursive generator.

Test Data.

At its bare basics can walk a tree and yield all objects. List comprehension is used on result to keep or weed out by some condition.

import bpy

context = bpy.context

def walk_children(ob):
    yield ob
    for child in ob.children:
        yield from walk_children(child)
    
# test call
print("-" * 20)
print([o.name for o in walk_children(context.object) if o.type == 'MESH'])

Output.

--------------------
['Sphere', 'Cube', 'Cube.002', 'Cube.001', 'Cube.003']

A dictionary by type

One of my favourite things is the defaultdict type from collections module.

from collections import defaultdict
descendants = defaultdict(list)
for o in walk_children(context.object):
    descendants[o.type].append(o)
    
for k, obs in descendants.items():
    print(k, [o.name for o in obs])

Output:

EMPTY ['Empty']
ARMATURE ['Armature']
MESH ['Sphere', 'Cube', 'Cube.002', 'Cube.001', 'Cube.003']
CAMERA ['Camera', 'Camera.001']

With levels and yield only of a type

import bpy

context = bpy.context

def walk_children(ob, level=0, max_level=50, type='MESH'):
    print(f"{'  ' * level}{ob.name}")
    if ob.type == type:
        yield ob
    if level < max_level:
        for child in ob.children:
            yield from walk_children(child, level=level + 1)
    
# test call
print("-" * 20)
print([o.name for o in walk_children(context.object)])

Output.

--------------------
Empty
  Armature
    Sphere
      Camera
  Cube
    Cube.002
      Camera.001
        Cube.001
    Cube.003
    Lamp

['Sphere', 'Cube', 'Cube.002', 'Cube.001', 'Cube.003']

A "wrapper"

As also demonstrated in the upvoteworthy answer of @lemon

If we wrap our recursive generator above in a method, can pass initial arguments, and manipulate the result.

Example below uses the absolute scene depth of the root object passed to find the names of all objects at a particular depth.

def walk_children(ob, min_level=0, max_level=100):
    def get_level(ob):
        i = 0
        while(ob.parent):
            i += 1
            ob = ob.parent
        return i
    def _walk_children(ob, level=get_level(ob)):

        yield level, ob.name
        for child in ob.children:
            yield from _walk_children(child, level=level + 1)
    # 
    return list(o for lev, o in _walk_children(ob)
        if min_level <= lev <= max_level)
    
# test call
root_obs = [o for o in context.scene.objects 
        if o.parent is None]
for root in root_obs:
    print(root.name, walk_children(root, min_level=2, max_level=2))
       

Output, name of all objects in scene with a depth two ancestors.

Empty ['Sphere', 'Cube.002', 'Cube.003', 'Lamp']
Top answer
1 of 1
2

Below is a table where each column represents a stack frame (left to right). A tuple indicates what the local variables remaining and start have as values (they are local and never change value). The value of i is implicitly represented by showing the argument with which append is executed.

At the far right combination is represented, which is a single list to which all executions of find_combinations_recursively have access. An asterisk suffix means that combination is printed.

For input 5:

depth 0 depth 1 depth 2 depth 3 depth 4 depth 5 combination
(5, 1) []
append 1 [1]
(4, 1) [1]
append 1 [1, 1]
(3, 1) [1, 1]
append 1 [1, 1, 1]
(2, 1) [1, 1, 1]
append 1 [1, 1, 1, 1]
(1, 1) [1, 1, 1, 1]
append 1 [1, 1, 1, 1, 1]
(0, 1) [1, 1, 1, 1, 1]*
pop [1, 1, 1, 1]
pop [1, 1, 1]
append 2 [1, 1, 1, 2]
(0, 2) [1, 1, 1, 2]*
pop [1, 1, 1]
pop [1, 1]
append 2 [1, 1, 2]
(1, 2) [1, 1, 2]
pop [1, 1]
append 3 [1, 1, 3]
(0, 3) [1, 1, 3]*
pop [1, 1]
pop [1]
append 2 [1, 2]
(2, 2) [1, 2]
append 2 [1, 2, 2]
(0, 2) [1, 2, 2]*
pop [1, 2]
pop [1]
append 3 [1, 3]
(1, 3) [1, 3]
pop [1]
append 4 [1, 4]
(0, 4) [1, 4]*
pop [1]
pop []
append 2 [2]
(3, 2) [2]
append 2 [2, 2]
(1, 2) [2, 2]
pop [2]
append 3 [2, 3]
(0, 3) [2, 3]*
pop [2]
pop []
append 3 [3]
(2, 3) [3]
pop []
append 4 [4]
(1, 4) [4]
pop []
append 5 [5]
(0, 5) [5]*
pop []

For input 3:

depth 0 depth 1 depth 2 depth 3 combination
(3, 1) []
append 1 [1]
(2, 1) [1]
append 1 [1, 1]
(1, 1) [1, 1]
append 1 [1, 1, 1]
(0, 1) [1, 1, 1]*
pop [1, 1]
pop [1]
append 2 [1, 2]
(0, 2) [1, 2]*
pop [1]
pop []
append 2 [2]
(1, 2) [2]
pop []
append 3 [3]
(0, 3) [3]*
pop []

I hope this clarifies what happens.

🌐
Stack Overflow
stackoverflow.com › questions › 72790108 › how-can-i-write-a-function-that-generates-children-from-parents-in-a-recursive-w
python - How can i write a function that generates children from parents in a recursive way? - Stack Overflow
# # As new instances of the same node will not be generated, nodes # might come from different parents at the same time. # Edit: By Setting add_child_of_self as True, parents can now add a child with their own label into the graph.