Related
I need to find a list of lists when traversing through a list of objects in python and need to create every possible path
The output should look something like this
[[24,137,237], [24,137,251], [24,151,155], [24,151,155,154]]
The input looks like this:
[
{
"id": 24,
"children": [
{
"id": 137,
"children": [
{
"id": 237,
"children": [],
},
{
"id": 251,
"children": [],
}
],
},
{
"id": 151,
"children": [
{
"id": 155,
"children": [],
},
{
"id": 154,
"children": [],
}
],
},
],
},
]
Current code does something similar but not quite the desired result.
def create_category_paths_from_children(
children: List[CategoriesTreeNodeFull], category_paths_list: List[CategoryPath]
) -> List[CategoryPath]:
for child in children:
category_paths_list.append(CategoryPath(name=child.name, id=child.id))
if len(child.children) > 0:
create_category_paths_from_children(child.children, category_paths_list)
return category_paths_list
Edit: Updated my current code.
Here's the solution I came up with:
def getAllPaths(tree, upto = [], current_trees = []):
upto = upto + [tree.id]
if len(tree.children) == 0:
current_trees.append(upto)
else:
for branch in tree.children:
getAllPaths(branch, upto, current_trees)
return current_trees
This is assuming the "object" you described looks something like the following in python code:
class Tree:
id = 0 # Some int
children = [] # Some list of Tree objects
You can consider the recursive approach, the principle is to keep executing update_path until no children exist, when the condition is met, directly call the global variables to append the list elements, otherwise keep recursively executing down and update ancestors, ancestors、 children、 id represent all parent id list, children list, the current level id value:
items = [
{
"id": 24,
"children": [
{
"id": 137,
"children": [
{
"id": 237,
"children": [],
},
{
"id": 251,
"children": [],
}
],
},
{
"id": 151,
"children": [
{
"id": 155,
"children": [],
},
{
"id": 154,
"children": [],
}
],
},
],
},
]
class Node:
def __init__(self, nid, ancestors, children):
self.ancestors = ancestors
self.children = children
self.id = nid
def update_path(self):
if not self.children:
global res
res.append(self.ancestors + [self.id])
else:
for _item in self.children:
node = Node(_item["id"], self.ancestors + [self.id], _item.get("children", []))
node.update_path()
res = []
for item in items:
Node(item["id"], [], item["children"]).update_path()
print(res)
OUTPUT:
[[24, 137, 237], [24, 137, 251], [24, 151, 155], [24, 151, 154]]
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 2 years ago.
Improve this question
I have a list of json objects like this -
[{Level1: "A", Level2: "B", Level3: "C", Level4: "item1"},
{Level1: "A", Level2: "B", Level3: null, Level4: "item2"},
{Level1: "D", Level2: null, Level3: null, Level4: "item3"}]
In Python, I want to group them by level to create a tree structure.
{text: "root": items:
[{text: "A", items: [
{text: "B", items: [
{text: "C", items: [
text: "item1", items:[]]},
{text: "item2", items: []}}]},
{text: "D", items: [{text: "item3", items: []}]}
]
]}
# pseudocode
result = dict()
result["text"] = "root"
result["items"] = []
d = {"Level1": set(), "Level2": set(), "Level3": set(), "Level4": set()}
for row in data_rows:
insertLocation = result["items"]
for key in ["Level1", "Level2", "Level3", "Level4"]:
txt = row[key]
if txt in d[key]:
for j in insertLocation:
if j.text = txt:
insertLocation = j
break
else:
newItem = {"text": txt, "items": []}
insertLocation = newItem.items
d[key].add(txt)
Can anyone provide any feedback on my code to perhaps make it more efficient? (or if there's a better way to do this, would be super great to know). I'm really looking to maximize efficiency.
Here is how I would suggest doing it.
To understand this, you will have to understand:
functools.reduce
collections.defaultdict
Recursion
List comprehensions
Basically, I first change the input (because the keys don't matter), to be a list of lists of non-null values.
Then I functools.reduce the lists of non-null values into a simple tree structure which is of the shape that we want.
Everything in this simple_tree is a collections.defaultdict so I finally convert these into normal dicts and use recursion to get the "text"/"items" structure that you want.
This approach is more "functional" than your approach, and is smarter about pruning the input.
import collections
import functools
sequence = [
{"Level1": "A", "Level2": "B", "Level3": "C", "Level4": "item1"},
{"Level1": "A", "Level2": "B", "Level3": None, "Level4": "item2"},
{"Level1": "D", "Level2": None, "Level3": None, "Level4": "item3"},
]
# Change the input to be only the non-null values (order is preserved)
# I am using 2 list comprehensions here
seq_values = [[y for y in x.values() if y is not None] for x in sequence]
# Define a recursive defaultdict
# https://stackoverflow.com/questions/19189274/nested-defaultdict-of-defaultdict
def recursive_defaultdict():
return collections.defaultdict(recursive_defaultdict)
# Use reduce to get a simple tree in the shape/structure we want
def reduce_function(agg, cur):
sub_agg = agg
for seq_value in cur:
sub_agg = sub_agg[seq_value]
return agg
# Use reduce to get a simple tree in the shape/structure we want
simple_tree = functools.reduce(
reduce_function,
seq_values,
recursive_defaultdict()
)
# Use recursion to change simple "defaultdict tree" into custom text/items tree
def convert_defaultdict_tree_to_custom_dict(dd_tree):
if dd_tree:
return [{"text": x, "items": convert_defaultdict_tree_to_custom_dict(y)} for x, y in dd_tree.items()]
return []
# Here is the final_result, the thing you want.
final_result = {
"text": "root",
"items": convert_defaultdict_tree_to_custom_dict(simple_tree)
}
# Test the final_result is correct
expected_result = {
"text": "root",
"items": [
{
"text": "A",
"items": [
{
"text": "B",
"items": [
{
"text": "C",
"items": [
{"text": "item1", "items": []},
],
}, {
"text": "item2",
"items": [],
}
],
}
],
},
{"text": "D", "items": [{"text": "item3", "items": []}]},
],
}
assert final_result == expected_result
You can see at the end I do an assert to make sure the final_result is what we want. Let me know if you want help understanding any of this.
I suggest you check this answer by #anom on "What are some good code optimization methods?". In summary:
Step 1. Do not think about performance, think about clarity and correctness.
...
Step 4. See step 1.
Here's an alternative:
data_rows = [
{"Level1": "A", "Level2": "B", "Level3": "C", "Level4": "item1"},
{"Level1": "A", "Level2": "B", "Level3": None, "Level4": "item2"},
{"Level1": "D", "Level2": None,"Level3": None, "Level4": "item3"}
]
result = { "root": {} }
for row in data_rows:
current_level = result["root"]
# go through every "level" (key) in the row except the last one
for level in row.keys()[:-1]:
# make sure the level is not None
if row[level]:
# check if the level already exists
if row[level] not in current_level:
# if it doesn't, create this level
current_level[row[level]] = {}
# we move to the new level
current_level = current_level[row[level]]
# we set the "item" in the last valid level
current_level["items"]=row[row.keys()[-1]]
import json
print(json.dumps(result, indent=4))
This will create a tree as below:
{
"root": {
"A": {
"B": {
"items": "item2",
"C": {
"items": "item1"
}
}
},
"D": {
"items": "item3"
}
}
}
So instead of having a separate key "text" with the level name, the level name itself becomes the key.
The above code will work with any number of levels, as long as the last item in every data_row is the "item"
I am having the following problem.
class Inventory:
def __init__(self,project_no,country,category,product,count):
self.project_no = project_no
self.country = country
self.category = category
self.product = product
self.count = count
inventory_list = []
inventory_list.append(Inventory(1,'USA','Beverages','Milk',2))
inventory_list.append(Inventory(1,'USA','Beverages','Juice',5))
inventory_list.append(Inventory(1,'USA','Snacks','Potato Chips',2))
inventory_list.append(Inventory(1,'USA','Oils','Canola',5))
inventory_list.append(Inventory(1,'USA','Oils','Olive',8))
inventory_list.append(Inventory(1,'CAN','Beverages','Milk',7))
inventory_list.append(Inventory(1,'CAN','Beverages','Juice',8))
inventory_list.append(Inventory(1,'CAN','Snacks','Potato Chips',8))
inventory_list.append(Inventory(1,'CAN','Oils','Canola',3))
inventory_list.append(Inventory(1,'CAN','Oils','Olive',4))
{'Inventory': [{'Country': inv.country , 'Category' : [{inv.category : [{'Product' : inv.product}]}] } for inv in inventory_list]}
This code is giving me the following output.
{'Inventory': [{'Country': 'USA',
'Category': [{'Beverages': [{'Product': 'Milk'}]}]},
{'Country': 'USA', 'Category': [{'Beverages': [{'Product': 'Juice'}]}]},
{'Country': 'USA', 'Category': [{'Snacks': [{'Product': 'Potato Chips'}]}]},
{'Country': 'USA', 'Category': [{'Oils': [{'Product': 'Canola'}]}]},
{'Country': 'USA', 'Category': [{'Oils': [{'Product': 'Olive'}]}]},
{'Country': 'CAN', 'Category': [{'Beverages': [{'Product': 'Milk'}]}]},
{'Country': 'CAN', 'Category': [{'Beverages': [{'Product': 'Juice'}]}]},
{'Country': 'CAN', 'Category': [{'Snacks': [{'Product': 'Potato Chips'}]}]},
{'Country': 'CAN', 'Category': [{'Oils': [{'Product': 'Canola'}]}]},
{'Country': 'CAN', 'Category': [{'Oils': [{'Product': 'Olive'}]}]}]}
What I actually need is more like below.
{
"Inventory": [{
"country": "USA",
"category": [{
"Beverages": [{
"product": "Milk",
"count": 2
}, {
"product": "Juice",
"count": 5
}]
}, {
"Snacks": [{
"product": "Potato Chips",
"count": 2
}]
}, {
"Oils": [{
"product": "Canola",
"count": 5
}, {
"product": "Olive",
"count": 8
}]
}]
}, {
"country": "CAN",
"category": [{
"Beverages": [{
"product": "Milk",
"count": 7
}, {
"product": "Juice",
"count": 8
}]
}, {
"Snacks": [{
"product": "Potato Chips",
"count": 8
}]
}, {
"Oils": [{
"product": "Canola",
"count": 3
}, {
"product": "Olive",
"count": 4
}]
}]
}
]
}
How to do this?
I thought list comprehension is the way to go.
But I am having trouble beyond this point.
I thought this should be really easy for a python coder.
With my limited python I could only reach this far.
If anyone can help.
I would suggest you try serializing your Inventory class using the json module. However, it looks like you'll want to reorganize your data a bit. From what I can tell, you want to have an inventory that has a collection of countries which contain a set of products separated into categories.
First, let's define the Product class:
class Product(object):
def __init__(self, name, count):
self.product = name
self.count = count
Next, we can define the Country class as a container for a set Products, arranged in a dictionary using the category name as the key.
class Country(object):
def __init__(self, name):
self.name = name
self.categories = dict()
def add_product_to_category(self, category, product):
if category not in self.categories:
self.categories[category] = []
self.categories[category].append(product)
Then, we can re-define the Inventory class as a container for a set of Country objects.
class Inventory(object):
def __init__(self, project_no):
self.project_no = project_no
self.countries = []
Next, we can use simple methods to fill out our classes with the required data.
inv = Inventory(1)
us_set = Country('USA')
us_set.add_product_to_category('Beverages', Product('Milk', 2))
us_set.add_product_to_category('Beverages', Product('Juice', 5))
us_set.add_product_to_category('Snacks', Product('Potato Chips', 2))
us_set.add_product_to_category('Oils', Product('Canola', 5))
us_set.add_product_to_category('Oils', Product('Olive', 8))
canada_set = Country('CAN')
canada_set.add_product_to_category('Beverages', Product('Milk', 7))
canada_set.add_product_to_category('Beverages', Product('Juice', 8))
canada_set.add_product_to_category('Snacks', Product('Potato Chips', 8))
canada_set.add_product_to_category('Oils', Product('Canola', 3))
canada_set.add_product_to_category('Oils', Product('Olive', 4))
inv.countries.append(us_set)
inv.countries.append(canada_set)
Finally, (to actually answer your question, lul) to serialize the Inventory class, we have to define an encoder to use:
class MyEncoder(json.JSONEncoder):
def default(self, o):
return o.__dict__
Now, we can just call json.dumps() to get a string output of our serialized Inventory.
json.dumps(inv, indent=2, cls=MyEncoder)
The output isn't exactly what you laid out, but I think this method will work well for you.
{
"project_no": 1,
"countries": [
{
"name": "USA",
"categories": {
"Beverages": [
{
"count": 2,
"product": "Milk"
},
{
"count": 5,
"product": "Juice"
}
],
"Oils": [
{
"count": 5,
"product": "Canola"
},
{
"count": 8,
"product": "Olive"
}
],
"Snacks": [
{
"count": 2,
"product": "Potato Chips"
}
]
}
},
{
"name": "CAN",
"categories": {
"Beverages": [
{
"count": 7,
"product": "Milk"
},
{
"count": 8,
"product": "Juice"
}
],
"Oils": [
{
"count": 3,
"product": "Canola"
},
{
"count": 4,
"product": "Olive"
}
],
"Snacks": [
{
"count": 8,
"product": "Potato Chips"
}
]
}
}
]
}
try using the json module, e.g.
import json
...
inv_json = {'Inventory': [{'Country': inv.country , 'Category' : [{inv.category : [{'Product' : inv.product}]}] } for inv in inventory_list]}
json_formatted_str = json.dumps(x, indent=2)
print(json_formatted_str)
https://www.journaldev.com/33302/python-pretty-print-json
If my JSON data looks like this:
{
"name": "root",
"children": [
{
"name": "a",
"children": [
{
"name": "b",
"children": [
{
"name": "c",
"size": "1"
},
{
"name": "d",
"size": "2"
}
]
},
{
"name": "e",
"size": 3
}
]
},
{
"name": "f",
"children": [
{
"name": "g",
"children": [
{
"name": "h",
"size": "1"
},
{
"name": "i",
"size": "2"
}
]
},
{
"name": "j",
"size": 5
}
]
}
]
}
How can I return two adjacent levels in Python?
For example return:
a - b,e
f - g,j
The data could become very large, therefore I have to slice it into smaller pieces.
Thanks for every help.
You need to build a tree of dicts, with values as the leaves:
{'a': {'b': {'c': '1', 'd': '2'}, 'e': '3'}, 'f': {'g': {'h': '1', 'i': '2'}, 'j': '5'}}
This can be decomposed into three separate actions:
get the "name" of a node for use as a key
if the node has "children", transform them to a dict
if the node has a "size", transform that to the single value
Unless your data is deeply nested, recursion is a straightforward approach:
def compress(node: dict) -> dict:
name = node['name'] # get the name
try:
children = node['children'] # get the children...
except KeyError:
return {name: node['size']} # or return name and value
else:
data = {}
for child in children: # collect and compress all children
data.update(compress(child))
return {name: data}
This compresses the entire hierarchy, including the "root" node:
>>> compress(data)
{'root': {'a': {'b': {'c': '1', 'd': '2'}, 'e': 3},
'f': {'g': {'h': '1', 'i': '2'}, 'j': 5}}}
Try this solution, tell me this works or not.
dictVar = {
"name": "root",
"children": [
{
"name": "a",
"children": [
{
"name": "b",
"children": [
{
"name": "c",
"size": "1"
},
{
"name": "d",
"size": "2"
}
]
},
{
"name": "e",
"size": 3
}
]
},
{
"name": "f",
"children": [
{
"name": "g",
"children": [
{
"name": "h",
"size": "1"
},
{
"name": "i",
"size": "2"
}
]
},
{
"name": "j",
"size": 5
}
]
}
]
}
name = {}
for dobj in dictVar['children']:
for c in dobj['children']:
if not dobj['name'] in name:
name[dobj['name']] = [c['name']]
else:
name[dobj['name']].append(c['name'])
print(name)
AND as you need all origin data then another is :
name = {}
for dobj in dictVar['children']:
for c in dobj['children']:
if not dobj['name'] in name:
name[dobj['name']] = [c]
else:
name[dobj['name']].append(c)
print(name)
I have 2 dictionaries in python (d1, d2) where I need to pass the missing "id" item from d2 to d1, ignoring any other differences (such as the extra "child" in d1). What effectively is needed, is that a result dictionary is just d1 with "id" items added. I have tried merging, but it did not work since either way I lose data.
d1 = {
"parent": {
"name": "Axl",
"surname": "Doe",
"children": [
{
"name": "John",
"surname": "Doe"
},
{
"name": "Jane",
"surname": "Doe",
"children": [
{
"name": "Jim",
"surname": "Doe"
},
{
"name": "Kim",
"surname": "Doe"
}
]
}
]
}
}
d2 = {
"parent": {
"id": 1,
"name": "Axl",
"surname": "Doe",
"children": [
{
"id": 2,
"name": "John",
"surname": "Doe"
},
{
"id": 3,
"name": "Jane",
"surname": "Doe",
"children": [
{
"id": 4,
"name": "Jim",
"surname": "Doe"
},
{
"id": 5,
"name": "Kim",
"surname": "Doe"
},
{
"id": 6
"name": "Bill",
"surname": "Doe"
},
]
}
]
}
}
result = {
"parent": {
"id": 1,
"name": "Axl",
"surname": "Doe",
"children": [
{
"id": 2,
"name": "John",
"surname": "Doe"
},
{
"id": 3,
"name": "Jane",
"surname": "Doe",
"children": [
{
"id": 4,
"name": "Jim",
"surname": "Doe"
},
{
"id": 5,
"name": "Kim",
"surname": "Doe"
}
]
}
]
}
}
Any ideas?
I match children according to a key function, in this case "name" and "surname" attributes.
I then go over the id_lookup dict (named d2 in your example) and try to match each child with main_dict's children. If I find a match, I recurse into it.
In the end, main_dict (or d1 in your example) is filled with IDs :-)
import operator
root = main_dict["parent"]
lookup_root = id_lookup_dict["parent"]
keyfunc = operator.itemgetter("name", "surname")
def _recursive_fill_id(root, lookup_root, keyfunc):
"""Recursively fill root node with IDs
Matches nodes according to keyfunc
"""
root["id"] = lookup_root["id"]
# Fetch children
root_children = root.get("children")
# There are no children
if root_children is None:
return
children_left = len(root_children)
# Create a dict mapping the key identifying a child to the child
# This avoids a hefty lookup cost and requires a single iteration.
children_dict = dict(zip(map(keyfunc, root_children), root_children))
for lookup_child in lookup_root["children"]:
lookup_key = keyfunc(lookup_child)
matching_child = children_dict.get(lookup_key)
if matching_child is not None:
_recursive_fill_id(matching_child, lookup_child, keyfunc)
# Short circuit in case all children were filled
children_left -= 1
if not children_left:
break
_recursive_fill_id(root, lookup_root, keyfunc)
I wished to add an iterative answer instead of the recursive answer, as it'll probably prove to be more efficient.
It will not reach any stack threshold and will be a bit faster:
import operator
root = main_dict["parent"]
lookup_root = id_lookup_dict["parent"]
keyfunc = operator.itemgetter("name", "surname")
def _recursive_fill_id(root, lookup_root, keyfunc):
"""Recursively fill root node with IDs
Matches nodes according to keyfunc
"""
matching_nodes = [(root, lookup_root)]
while matching_nodes:
root, lookup_root = matching_nodes.pop()
root["id"] = lookup_root["id"]
# Fetch children
root_children = root.get("children")
# There are no children
if root_children is None:
continue
children_left = len(root_children)
# Create a dict mapping the key identifying a child to the child
# This avoids a hefty lookup cost and requires a single iteration.
children_dict = dict(zip(map(keyfunc, root_children), root_children))
for lookup_child in lookup_root["children"]:
lookup_key = keyfunc(lookup_child)
matching_child = children_dict.get(lookup_key)
if matching_child is not None:
matching_nodes.append((matching_child, lookup_child))
# Short circuit in case all children were filled
children_left -= 1
if not children_left:
break
_recursive_fill_id(root, lookup_root, keyfunc)