Appending nodes of tree to a python list - python

I have a tree like this:
A
/ | \
B C D
/ \ | / | \
E F G H I J
and I am trying to append the nodes of the tree to an empty list
such that the list looks like:
[[A], [B, C, D], [E, F, G, H, I, J]]
Suppose that I have a root_node A and I don't know how deep my tree is.
How can I append nodes from the tree to an empty list in the above-mentioned format?
I tried breadth first search, but my list length is way longer than than the
depth of the tree.

Append each new depth of nodes as a new list.
Start with an empty list: tree = []
Create a new inner list for the current depth
Append each element at the current depth in the list: tree[depth].append(element)
Go to the next depth and repeat

Given a Node implementation like:
class Node:
def __init__(self, name):
self.name = name
self.children = []
def __repr__(self):
return f'Node({self.name})'
You can create your nodes and arrange a graph with:
nodes = {letter: Node(letter) for letter in 'ABCDEFGHIJ'}
nodes['A'].children.extend([nodes['B'], nodes['C'], nodes['D']])
nodes['B'].children.extend([nodes['E'], nodes['F']])
nodes['C'].children.extend([nodes['G']])
nodes['D'].children.extend([nodes['H'], nodes['I'], nodes['J']])
Now you can start with the root node, and continually make a new list of nodes until you run out with a simple generator:
def make_lists(root):
current = [root]
while current:
yield current
current = [c for n in current for c in n.children]
list(make_lists(nodes['A']))
The while loop will end when there are no more children, resulting in:
[[Node(A)],
[Node(B), Node(C), Node(D)],
[Node(E), Node(F), Node(G), Node(H), Node(I), Node(J)]]

Related

Find the width of tree at each level/height (non-binary tree)

Dear experienced friends, I am looking for an algorithm (Python) that outputs the width of a tree at each level. Here are the input and expected outputs.
(I have updated the problem with a more complex edge list. The original question with sorted edge list can be elegantly solved by #Samwise answer.)
Input (Edge List: source-->target)
[[11,1],[11,2],
[10,11],[10,22],[10,33],
[33,3],[33,4],[33,5],[33,6]]
The tree graph looks like this:
10
/ | \
11 22 33
/ \ / | \ \
1 2 3 4 5 6
Expected Output (Width of each level/height)
[1,3,6] # according to the width of level 0,1,2
I have looked through the web. It seems this topic related to BFS and Level Order Traversal. However, most solutions are based on the binary tree. How can solve the problem when the tree is not binary (e.g. the above case)?
(I'm new to the algorithm, and any references would be really appreciated. Thank you!)
Build a dictionary of the "level" of each node, and then count the number of nodes at each level:
>>> from collections import Counter
>>> def tree_width(edges):
... levels = {} # {node: level}
... for [p, c] in edges:
... levels[c] = levels.setdefault(p, 0) + 1
... widths = Counter(levels.values()) # {level: width}
... return [widths[level] for level in sorted(widths)]
...
>>> tree_width([[0,1],[0,2],[0,3],
... [1,4],[1,5],
... [3,6],[3,7],[3,8],[3,9]])
[1, 3, 6]
This might not be the most efficient, but it requires only two scans over the edge list, so it's optimal up to a constant factor. It places no requirement on the order of the edges in the edge list, but does insist that each edge be (source, dest). Also, doesn't check that the edge list describes a connected tree (or a tree at all; if the edge list is cyclic, the program will never terminate).
from collections import defauiltdict
# Turn the edge list into a (non-binary) tree, represented as a
# dictionary whose keys are the source nodes with the list of children
# as its value.
def edge_list_to_tree(edges):
'''Given a list of (source, dest) pairs, constructs a tree.
Returns a tuple (tree, root) where root is the root node
and tree is a dict which maps each node to a list of its children.
(Leaves are not present as keys in the dictionary.)
'''
tree = defaultdict(list)
sources = set() # nodes used as sources
dests = set() # nodes used as destinations
for source, dest in edges:
tree[source].append(dest)
sources.add(source)
dests.add(dest)
roots = sources - dests # Source nodes which are not destinations
assert(len(roots) == 1) # There is only one in a tree
tree.default_factory = None # Defang the defaultdict
return tree, roots.pop()
# A simple breadth-first-search, keeping the count of nodes at each level.
def level_widths(tree, root):
'''Does a BFS of tree starting at root counting nodes at each level.
Returns a list of counts.
'''
widths = [] # Widths of the levels
fringe = [root] # List of nodes at current level
while fringe:
widths.append(len(fringe))
kids = [] # List of nodes at next level
for parent in fringe:
if parent in tree:
for kid in tree[parent]:
kids.append(kid)
fringe = kids # For next iteration, use this level's kids
return widths
# Put the two pieces together.
def tree_width(edges):
return level_widths(*edge_list_to_tree(edges))
Possible solution that is based on Width-First-Traversal
In Width-First-Traversal we add the node to the array, but in this solution we put the array in an object together with its level and then add it to the array.
function levelWidth(root) {
const counter = [];
const traverseBF = fn => {
const arr = [{n: root, l:0}];
const pushToArr = l => n => arr.push({n, l});
while (arr.length) {
const node = arr.shift();
node.n.children.forEach(pushToArr(node.l+1));
fn(node);
}
};
traverseBF(node => {
counter[node.l] = (+counter[node.l] || 0) + 1;
});
return counter;
}

Create tasks from lists - Airflow

I have a requirement where I have a list of lists like
[[a,b,c,d],[e,f,g,h],[i,j,k,l]]
Now I want to create tasks in DAG like below
a >> b >> c >> d
e >> f >> g >> h
i >> j >> k >> l
Any help is appreciated.
You can use the handy chain() function to do this in 1 line.
from airflow.models.baseoperator import chain
[a,b,c,d,e,f,g,h,i,j,k,l] = [DummyOperator(task_id=f"{i}") for i in "abcdefghijkl"]
chain([a,e,i], [b,f,j], [c,g,k], [d,h,l])
Assuming a,b,c,... are operators - the below should do the job (mocking airflow operator)
class Operator:
def __init__(self, name):
self.name = name
def set_downstream(self, other):
print(f'{self}: setting {other} as downstream')
def __str__(self) -> str:
return self.name
a = Operator('a')
b = Operator('b')
c = Operator('c')
d = Operator('d')
e = Operator('e')
f = Operator('f')
lst = [[a, b, c], [e, f, d]]
for oper_lst in lst:
for i in range(0, len(oper_lst) - 1):
oper_lst[i].set_downstream(oper_lst[i + 1])
output
a: setting b as downstream
b: setting c as downstream
e: setting f as downstream
f: setting d as downstream
op_lists = [[a,b,c,d],[e,f,g,h],[i,j,k,l]]
for op_list in op_lists:
for i in range(len(op_list) - 1):
op_list[i] >> op_list[i + 1]
EDIT: I didn't see balderman's answer. He was first
With a,...,l being nodes and >> describing the arcs, we can write the nested list as a dictionary and then use this object for a directed acyclic graph. Depending on your data (e.g) the nested lists and the connections between them you can adapt the code. In the example above, we have three lists converting to three arcs. One could the list object to a dictionary like this:
graph = [["a","b","c","d"],["e","f","g","h"],["i","j","k","l"]]
dic_list = []
z = {}
for i in range(len(graph)):
b = dict(zip(graph[i][::1], list(graph[i][1::1])))
dic_list.append(b)
z = {**z, **dic_list[i]}
and then use this standard code from the python documentation to build a DAG out of it like this:
def find_all_paths(graph, start, end, path=[]):
path = path + [start]
if start == end:
return [path]
if not graph.has_key(start):
return []
paths = []
for node in graph[start]:
if node not in path:
newpaths = find_all_paths(graph, node, end, path)
for newpath in newpaths:
paths.append(newpath)
return paths
Does this answer your question?

print elements's list of an instance of class in python

Given the code below, i only have one issue. I try to print the elements's list (list_fils) of instances of class "Noeud" but it gives me this (example for Node 0):
Children of Node 0 are: [<__main__.Noeud object at 0x7f7d1a5009b0>, <__main__.Noeud object at 0x7f7d1a500b38>]
what can i do to print the elements of that list ? Here's my program:
class Noeud:
def __init__(self,val):
self.val=val
self.list_fils=[] #list containing children of the instance
# K is number of levels of our binary tree
#if we have k=2 levels, we need to iterate until Node with index (2^0)-1=0
#if we have k=3 levels, we need to iterate until Node with index (2^0 + 2^1)-1 = 2
#if we have k=4 levels, we need to iterate until Node with index (2^0 + 2^1 + 2^2)-1=6
#if we have k levels, we need to iterate until Node with index (2^0+ 2^1 +.....+2^(k-2))
def stop_index_for_level(K):
Sum=0
for x in range(K-1):
Sum+=2**x
return Sum-1
#for each node(i) in l we add children of this node(i) to the list_fils attribute
def Exo3(K,l):
Debut=1
for i in range(stop_index_for_level(K)+1):
Fin=Debut+2
l[i].list_fils.extend(l[Debut:Fin])
Debut=Fin
#our binary tree example (Note: each level must be completed)
#0
#/ \
#1 2
#/\ /\
#3 4 5 6
List_Nodes=[Noeud(i) for i in range(7)] #[0,1,2,3,4,5,6]
Exo3(3,List_Nodes)
for x in range(len(List_Nodes)):
print("Children of Node ",List_Nodes[x].val," are: ",List_Nodes[x].list_fils)

Finding depth of a Python tree from a text file

I'm relatively new to python, and I was trying out some questions when I encountered this problem. A tree is defined in a text file in the following manner,
d:
e:
b: d e
c:
a: b c
So, I want to write a simple python script that finds the depth of this. I'm not able to figure out a strategy to work this out. Is there any algorithm or technique for this?
My strategy would be as follows:
Find elements with no children.
For each of these, find the parent. Determine if any elements have this parent as a child - if not, your length is two (2).
If so, find the parent of the parent. Repeat step 2, incrementing your length counter. Continue the process updating a counter with each step.
For your case:
d -> b -> a (len 3)
e -> b -> a (len 3)
c -> a (len 2)
This could be described as a 'bottom up' tree construction method/algorithm.
The tree format you've given has a nice property: if x is the child of y, then x is given before y in the file. So you can simply loop through the file once and read the depth into a dictionary. For example:
depth = {}
for line in f:
parent, children = read_node(line)
if children:
depth[parent] = max(depth.get(child,1) for child in children) + 1
Then just print depth['a'], as a is the root. Here read_node is a quick function to parse the parent and children from a line of the file:
def read_node(line):
parent, children = line.split(":")
return parent, children.split()
I'm not sure what you mean by depth, if it's how many steps you have to go to visit every node, you could use the Depth-First Search to see how long it takes to visit every node in the graph.
Here's a simple implementation:
text_tree = """d:
e:
b: d e
c:
a: b c"""
tree = {}
for line in text_tree.splitlines():
node, childs = line.split(":")
tree[node] = set(childs.split())
def dfs(graph, start):
visited, stack = [], [start]
while stack:
vertex = stack.pop()
if vertex not in visited:
visited.append(vertex)
stack.extend(graph[vertex])
return visited
result = dfs(tree,"a")
print "It took %d steps, to visit every node in tree, the path took was %s"%(len(result),result)
Which outputs:
It took 5 steps, to visit every node in tree, the path took was ['a', 'b', 'd', 'e', 'c']

Cycle detected while printing root to leaves path

I used the solution to this problem to print all root to leaves path for a n-ary tree I have.
Unfortunately, I suspect, there is a cycle in one of the branch of the tree due to which the program breaches the maximum recursion limit.
A
/ \
B C
| /\
D E F
|
A (back to root)
D again goes back to A
Please tell me how should I handle the cycle detection in the below program.
def paths(tree):
#Helper function
#receives a tree and
#returns all paths that have this node as root and all other paths
if tree is the empty tree:
return ([], [])
else: #tree is a node
root = tree.value
rooted_paths = [[root]]
unrooted_paths = []
for subtree in tree.children:
(useable, unueseable) = paths(subtree)
for path in useable:
unrooted_paths.append(path)
rooted_paths.append([root]+path)
for path in unuseable:
unrooted_paths.append(path)
return (rooted_paths, unrooted_paths)
def the_function_you_use_in_the_end(tree):
a,b = paths(tree)
return a+b
p.s: I tried using visited nodes logic for detection, but that is not very helpful as a node can be legitimately visited multiple numbers of time:
for Example:
A C
A C E
A C F
C is visited multiple numbers of times
Keep a set on nodes visited and test to make sure the next node to be visited is outside the set of previously visited nodes.

Categories

Resources