Get level of items in a nested list - python

Problem:
I have some linked data and I want to build a structure like this one on this picture :
and get the level of each item because in the future I will make some calculations by staring at the lowest level of my tree structure.
Expected Result:
I need to get a structure that gives me items per level :
level 0: A
level 1: A = B, C,D
level 2: D = E, F, G
level 3: E = H,I , J, K
what I have tried so far:
I've tried this recursive code to simulate the behavior but I'm unable to get items the level of items.
dict_item = {"A": ["B","C","D"], "D": ["E","F","G"], "E":["H","I","J"]}
def build_bom(product):
if not dict_item.get(product):
return product
else :
return [build_bom(x) for x in dict_item.get(product)]
print(build_bom("A"))
My output is a nested list like this :
['B', 'C', [['H', 'I', 'J'], 'F', 'G']]
My Question:
I'm not sure if this is the best approach to handle my problem.
And how to get the desired output?
here is the desired output :
[ {"parent_E":["H", "I", "J"]},
{"parent_D": ["E", "F", "G"]},
{"parent_A"} :["D","C","B"]},
]
A list of dictionaries ( where keys are parents and values are children), the first element in the list is the lowest level of my structure and the last is the highest element.
PS: This is a simulation but in future, I will have to works on large datasets with this code.
Any Help will be appreciated

This is how I will approach this problem. First, I'll generate the tree from your dict_item object.
dict_item = {"A": ["B","C","D"], "D": ["E","F","G"], "E":["H","I","J"]}
def build_tree(x):
if x in dict_item:
return {x: [build_tree(v) for v in dict_item[x]]}
else:
return x
tree = build_tree("A")
print(tree)
>>> {'A': ['B', 'C', {'D': [{'E': ['H', 'I', 'J']}, 'F', 'G']}]}
Then, do a breadth-first search on the tree. Each time we hit an element that has children, we append it to a list:
results = []
queue = [tree]
while queue:
x = queue.pop(0)
if isinstance(x, dict):
parent, children = list(x.items())[0]
results.append({'parent_' + parent: dict_item[parent]})
for child in children:
queue.append(child)
print(results)
>>> [{'parent_A': ['B', 'C', 'D']}, {'parent_D': ['E', 'F', 'G']}, {'parent_E': ['H', 'I', 'J']}]
Then all we need to do now, is to reverse the list:
print list(reversed(results))
>>> [{'parent_E': ['H', 'I', 'J']}, {'parent_D': ['E', 'F', 'G']}, {'parent_A': ['B', 'C', 'D']}]

Related

How to use recursion in heterogenous list and dict

I was learning about recursion in python and solved some common problems like factorial, iterating through nested lists etc.
While solving such problems, I came up with this challenge where you have to use recusion to traverse through a heterogeneous input(an input which contains single str elements, nested lists, dictionary etc).
So the challenge involves traversing through all the values in this input and replace a specified value with another one.
The input used here looks like this:
input = ['a', 'b', {'1':{'o':'a'}}, 'c', 'd', {'T': 'b', 'K': [1, 'a', 3, {'S':{'Z':'t'},'R':{'3':'a'}}, {'key':[66,'a',88]}, ['a', 'c']]}, ['a'], 3, 'r', 'a']
The input is a list which itself contains lists and dicts, and some of these lists and dicts are nested and also have one another in themselves.
And the output that I'm expecting to get is:
# this is the output that should be got at the end, after running the code
['#', 'b', {'1':{'o':'#'}}, 'c', 'd', {'T': 'b', 'K': [1, '#', 3, {'S':{'Z':'t'},'R':{'3':'#'}}, {'key':[66,'#',88]}, ['#', 'c']]}, ['#'], 3, 'r', '#']
# exactly like input but with all 'a' replaced with '#'
# of course we can use treat change the input to string and then use replace() of string module and get the output
# but then this wont be a challenge would it?
correct = ['a', 'b', 'a', 'c', 'd', 'b', 1, 'a', 3, 't', 'a', 66, 'a', 88, 'a', 'c', 'a', 3, 'r', 'a']
The code I wrote is:
remove = 'a'
replace = 'X'
output = []
def recall(input):
for item in input:
if isinstance(item, list):
recall(item)
elif isinstance(item, dict):
for entry in item.values():
recall(entry)
else:
if isinstance(input, dict) and item in input.keys():
if input[item]==remove:
input[item]=replace
output.append(input[item])
else:
output.append(input[item])
else:
if item==remove:
item=replace
output.append(item)
else:
output.append(item)
print(item)
recall(input)
print(output)
This produces the output:
['X', 'b', 'X', 'c', 'd', 'b', 1, 'X', 3, 't', 'X', 66, 'X', 88, 'X', 'c', 'X', 3, 'r', 'X']
# a single list with all the 'a' replaced but there are no dicts with their key value pairs in it
I havn't been able to find a way to achieve the desired output.
Am I doing something wrong? Or is there any way the desired output can be achieved with recursion?
You are appending all the values to a single global output variable regardless of whether they are from a nested dict or list. Instead, you need to get the function to return a value so that the function at the next level up can deal with it appropriately - appending a dict or list in the right position in the nested hierarchy. For example, you could do it this way:
def replace_values(item, replacement):
if isinstance(item, list):
return [replace_values(v, replacement) for v in item]
elif isinstance(item, dict):
return {k: replace_values(v, replacement) for k, v in item.items()}
# Alternatively, in case you need the dict keys to be replaced too:
# return {replacement.get(k, k): replace_values(v, replacement) for k, v in item.items()}
else:
return replacement.get(item, item)
input_list = ['a', 'b', {'1':{'o':'a'}}, 'c', 'd', {'T': 'b', 'K': [1, 'a', 3, {'S':{'Z':'t'},'R':{'3':'a'}}, {'key':[66,'a',88]}, ['a', 'c']]}, ['a'], 3, 'r', 'a']
print(replace_values(input_list, {"a": "#"}))
Note that the type (dict, list, or other) and ordering of the elements are the same in the return value as in the input item. This preserves the nested structure of the input.

Creating a random value in the dictionary while looping over a list and appending

I need to create a dictionary structure in the below format.
list_1 = [1,2,3,4,5]
list_2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
list_3 = random.sample(list_2 , random.randint(0,len(list_2))
Loop as the len(list_1) and need to loop using list_2 and then using the random sample created using list_3, assigning each value of the list_3 as a value in the inner dictionary below, while iterating.
*Needed dictionary format:
my_dict = { 1: { 1: a,
2:b,
3,c},
2: { 1: 'd',
2: 'g'},
3: {1, 'e',
2, 'f',
3, 'g'}
.....
}*
My code:
list_1 = [1,2,3,4,5]
list_2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
list_4= []
j= 1
while i <= len(list_1):
list_3 = random.sample(list_2 , random.randint(0,len(list_2))
for k in list_3:
my_dict= { i: { j: k,
}
}
j+=1
i+=1
list_3 = random.sample(list_2 , random.randint(0,len(list_2))
list_4.append(my_dict)
The jth value should increment after every iterating of list_3 and keep adding a new jth key + value (k)
After the loop ends of list_3, another sample list (list_3) should be created and the above same repeats in the new ith key and gets added to the dictionary.
I am not getting the required result and need help if anyone can fix the code.
Thank you!
You're overwriting your my_dict in every iteration of the loop. You only need to add a new subdict with key i:
list_1 = [1,2,3,4,5]
list_2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
my_dict = {}
for i in list_1:
list_3 = random.sample(list_2 , random.randint(0,len(list_2)))
my_dict[i] = dict(enumerate(list_3, 1))
So, you have a list of keys and a list of values and you want to create a dictionary with one entry for each key, where the value for each key is a dictionary with a random selection from values as values and sequential numerical keys, starting at 1.
keys = [1, 2, 3, 4, 5]
values = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
result = {
k: {n: v
for n, v in enumerate(random.sample(values, random.randint(0, len(values))), 1)
}
for k in keys }
This:
defines a dictionary;
result= {}
with an entry for each key k in keys;
result= {k: {} for k in keys}
with each dictionary having keys and values from an enumeration starting at 1;
{n: v for n, v in enumerate([], 1)}
with the values being a random sample from values;
random.sample(values, _)
with a length between 0 and all items in values.
random.sample(random.randint(0, len(values)))
And Python allows you to just turn the enumeration into a dict directly:
result = {
k: dict(enumerate(random.sample(values, random.randint(0, len(values))), 1))
for k in keys }
you can try this:
import random
list_1 = [1,2,3,4,5]
list_2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
for i in list_1:
list_3 = random.sample(list_2 , random.randint(0,len(list_2)))
my_dict[i]=dict(enumerate(list_3,1))
print(my_dict)

Generate all possible paths from a list of tuples

Given a list of tuples, I need to find all unique paths from this list :
Input: [('a','b'),('b','c'),('c','d'),('g','i'),('d','e'),('e','f'),('f','g'),('c','g')]
Output: [['a','b','c','d','e','f','g'],['a','b','c','g','i']]
(the 2 possible unique paths)
Two tuples can connect if the second element of the tuple matches with the first element of the other tuple i.e: One tuple is (_,a) and other tuple is like (a,_).
This issue has already been raised there: Getting Unique Paths from list of tuple but the solution is implemented in haskell (and I know nothing about this language).
But do you know if there's an efficient way to do this in Python?
I know the library itertools has many efficient built in functions for stuff like that, but I'm not too familiar with this.
You are wanting to find all simple paths in your graph.
Python has an amazing library for graph processing: networkx. You can solve your problem with literally several lines of code:
import networkx as nx
a = [('a','b'),('b','c'),('c','d'),('g','i'),('d','e'),('e','f'),('f','g'),('c','g')]
# Create graph
G = nx.Graph()
# Fill graph with data
G.add_edges_from(a)
# Get all simple paths from node 'a' to node 'i'
list(nx.all_simple_paths(G, 'a', 'i'))
will return you:
[['a', 'b', 'c', 'g', 'i'], ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i']]
If you want ALL possible paths, just replace the last line with it:
for start in G.nodes:
for end in G.nodes:
if start != end:
print(list(nx.all_simple_paths(G, start, end)))
You can build a dict that maps each parent to a list of connected children, so that you can recursively yield the paths from each parent node in an average time complexity of O(n):
def get_paths(parent, mapping):
if parent not in mapping:
yield [parent]
return
for child in mapping[parent]:
for path in get_paths(child, mapping):
yield [parent, *path]
edges = [('a','b'),('b','c'),('c','d'),('g','i'),('d','e'),('e','f'),('f','g'),('c','g')]
parents = set()
children = set()
mapping = {}
for a, b in edges:
mapping.setdefault(a, []).append(b)
parents.add(a)
children.add(b)
print([path for parent in parents - children for path in get_paths(parent, mapping)])
This outputs:
[['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i'], ['a', 'b', 'c', 'g', 'i']]
You can use recursion with a generator:
d = [('a','b'),('b','c'),('c','d'),('g','i'),('d','e'),('e','f'),('f','g'),('c','g')]
def get_paths(start, c = []):
r = [b for a, b in d if a == start]
if r:
for i in r:
yield from get_paths(i, c+[i])
else:
yield c
print(list(get_paths('a', ['a'])))
Output:
[['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i'], ['a', 'b', 'c', 'g', 'i']]

Python tree traversal from specific breadth

I've been trying to solve this probably very simple problem for a day now, and I still can't get my head around the problem.
Suppose we have a dictionary, e.g.
di = {'a' : ['b','c','d'],c : ['g','j','k'],k : ['z','y']}
I've been trying to write out only elements, which appear lower in the tree than an element specified. So for e.g if I call get_low('a',2), it will return the elements [g,j,k,z,y], so all elements down the a tree from the second level down.
My attempt:
def get_low(obj, level, children = [], generation = 0):
if level == 0:
children = [obj]
generation += 1
#print (generation, level)
for child in di[obj]:
if level <= generation:
children.append(child)
get_low(child, level,children, generation)
return set(children)
This although prints some of the correct values, two things don't work: 1.) it doesn't include obj if level is set at zero and 2.) if I set level to e.g. 4, it doesn't throw empty set() as a result.
Thanks for any help!
Well, I did it a bit different but I think it gets the right nodes:
di = {'a' : ['b','c','d'], 'c' : ['g','j','k'], 'k' : ['z','y']}
children = set()
def get_low(obj, level):
if level==0:
children.add(obj)
level -= 1
if di.get(obj):
for i in di.get(obj):
if level<=0:
children.add(i)
get_low(i,level)
else:
return
get_low('a', 2)
print children
For example for level=2 the result is:
set(['y', 'k', 'j', 'z', 'g'])
for level=0 is:
set(['a', 'c', 'b', 'd', 'g', 'k', 'j', 'y', 'z'])
and for level=4:
set([])

Python Remove SOME duplicates from a list while maintaining order?

I want to remove certain duplicates in my python list.
I know there are ways to remove all duplicates, but I wanted to remove only consecutive duplicates, while maintaining the list order.
For example, I have a list such as the following:
list1 = [a,a,b,b,c,c,f,f,d,d,e,e,f,f,g,g,c,c]
However, I want to remove the duplicates, and maintain order, but still keep the 2 c's and 2 f's, such as this:
wantedList = [a,b,c,f,d,e,f,g,c]
So far, I have this:
z = 0
j=0
list2=[]
for i in list1:
if i == "c":
z = z+1
if (z==1):
list2.append(i)
if (z==2):
list2.append(i)
else:
pass
elif i == "f":
j = j+1
if (j==1):
list2.append(i)
if (j==2):
list2.append(i)
else:
pass
else:
if i not in list2:
list2.append(i)
However, this method gives me something like:
wantedList = [a,b,c,c,d,e,f,f,g]
Thus, not maintaining the order.
Any ideas would be appreciated! Thanks!
Not completely sure if c and f are special cases, or if you want to compress consecutive duplicates only. If it is the latter, you can use itertools.groupby():
>>> import itertools
>>> list1
['a', 'a', 'b', 'b', 'c', 'c', 'f', 'f', 'd', 'd', 'e', 'e', 'f', 'f', 'g', 'g', 'c', 'c']
>>> [k for k, g in itertools.groupby(list1)]
['a', 'b', 'c', 'f', 'd', 'e', 'f', 'g', 'c']
To remove consecutive duplicates from a list, you can use the following generator function:
def remove_consecutive_duplicates(a):
last = None
for x in a:
if x != last:
yield x
last = x
With your data, this gives:
>>> list1 = ['a','a','b','b','c','c','f','f','d','d','e','e','f','f','g','g','c','c']
>>> list(remove_consecutive_duplicates(list1))
['a', 'b', 'c', 'f', 'd', 'e', 'f', 'g', 'c']
If you want to ignore certain items when removing duplicates...
list2 = []
for item in list1:
if item not in list2 or item in ('c','f'):
list2.append(item)
EDIT: Note that this doesn't remove consecutive items
EDIT
Never mind, I read your question wrong. I thought you were wanting to keep only certain sets of doubles.
I would recommend something like this. It allows a general form to keep certain doubles once.
list1 = ['a','a','b','b','c','c','f','f','d','d','e','e','f','f','g','g','c','c']
doubleslist = ['c', 'f']
def remove_duplicate(firstlist, doubles):
newlist = []
for x in firstlist:
if x not in newlist:
newlist.append(x)
elif x in doubles:
newlist.append(x)
doubles.remove(x)
return newlist
print remove_duplicate(list1, doubleslist)
The simple solution is to compare this element to the next or previous element
a=1
b=2
c=3
d=4
e=5
f=6
g=7
list1 = [a,a,b,b,c,c,f,f,d,d,e,e,f,f,g,g,c,c]
output_list=[list1[0]]
for ctr in range(1, len(list1)):
if list1[ctr] != list1[ctr-1]:
output_list.append(list1[ctr])
print output_list
list1 = ['a', 'a', 'b', 'b', 'c', 'c', 'f', 'f', 'd', 'd', 'e', 'e', 'f', 'f', 'g', 'g', 'c', 'c']
wantedList = []
for item in list1:
if len(wantedList) == 0:
wantedList.append(item)
elif len(wantedList) > 0:
if wantedList[-1] != item:
wantedList.append(item)
print(wantedList)
Fetch each item from the main list(list1).
If the 'temp_list' is empty add that item.
If not , check whether the last item in the temp_list is
not same as the item we fetched from 'list1'.
if items are different append into temp_list.

Categories

Resources