2

I would like to convert a nested list of dictionary into a substructure. And finding a robust way to do so. The structure:

nested_list = [
  {
     "id" : "fruit",
     "name" : "apple"
  },
  {
     "name": "fruit"
  },
  {
     "id" : "fruit",
     "name" : "grape"
  },
  {
     "id" : "fruit",
     "name" : "pineapple"
  },
  {
     "name": "vehicle"
  },
  {
    "id" : "vehicle",
     "name": "car"
  },
  {
    "id" : "car",
     "name": "sedan"
  },
] 

Into:

{
  "vehicle": {
    "car": {
        "sedan" : {}
     }
  },
  "fruit" : {
     "apple": {},
    "grape": {},
    "pineapple": {}
  }
}

Note that in this case it can go two level down. But It can go three deep down as well. For example one additional entry:

{ 
   "id" : "sedan", 
   "name": "mini sedan" 
}

My approach so far is:

for category in nested_list:
    if 'id' not in category:
        d[category['name']] = {}

for category in nested_list:
    if 'id' in category and category['id'] in d:
        d[category['id']][category['name']] = {}
    elif 'id' in category and category['id'] not in d:
        for k, v in d.items():
            if category['id'] in v:
                d[k][category['id']] = {category['name']: {}}
    # If there are not top level key then do nothing
    else:
        pass

It works in this case. The problem is it's not robust enough. I'm thinking recursion but unable to crack it. Can someone help out? Thank you

4
  • 1
    Hi, when you say "nested list", is that intentional? The list you provided is not nested. It contains dictionaries all at the same level. Commented Mar 30, 2020 at 14:18
  • Oh Im wrong. It's not nested list. But rather a strange structure with hierarchy Commented Mar 30, 2020 at 14:21
  • Can you share an example with problematic input ? Commented Mar 30, 2020 at 14:21
  • problematic input? The first list is the a potential input. I went to have a conversion function to return output like second dictionary Commented Mar 30, 2020 at 14:23

2 Answers 2

4

Solution

You can use collections.defaultdict and dict.setdefault:

from collections import defaultdict

nested_list = [
    {
        "id": "fruit",
        "name": "apple"
    },
    {
        "name": "fruit"
    },
    {
        "id": "fruit",
        "name": "grape"
    },
    {
        "id": "fruit",
        "name": "pineapple"
    },
    {
        "name": "vehicle"
    },
    {
        "id": "vehicle",
        "name": "car"
    },
    {
        "id": "car",
        "name": "sedan"
    },
    {
        "id": "sedan",
        "name": "mini sedan"
    },
]

working_dict = defaultdict(dict)
result_dict = {}

for item in nested_list:
    name = item['name']
    if 'id' in item:
        id_ = item['id']
        working_dict[id_].setdefault(name, working_dict[name])
    else:
        result_dict[name] = working_dict[name]
print(working_dict)
print(result_dict)

output:

defaultdict(<class 'dict'>, {'fruit': {'apple': {}, 'grape': {}, 'pineapple': {}}, 'apple': {}, 'grape': {}, 'pineapple': {}, 'vehicle': {'car': {'sedan': {'mini sedan': {}}}}, 'car': {'sedan': {'mini sedan': {}}}, 'sedan': {'mini sedan': {}}, 'mini sedan': {}})
{'fruit': {'apple': {}, 'grape': {}, 'pineapple': {}}, 'vehicle': {'car': {'sedan': {'mini sedan': {}}}}}

Explanation

  • The idea: dict is mutable.
  • working_dict is reference table for all "id"s.
  • If there is no such id, register {} for it.
  • And register elements without id field, as root elements into result_dict.

Append

If you don't want to use collections.defaultdict, you can only use dict.setdefault. But it is more verbose.

working_dict = {}
result_dict = {}

for item in nested_list:
    name = item['name']
    if 'id' in item:
        id_ = item['id']
        working_dict.setdefault(id_, {}).setdefault(name, working_dict.setdefault(name, {}))
    else:
        result_dict[name] = working_dict.setdefault(name, {})
print(result_dict)
Sign up to request clarification or add additional context in comments.

6 Comments

this is good. I thought using defaultdict as well. But I want a answer similar to: {'fruit': {'apple': {}, 'grape': {}, 'pineapple': {}}, 'vehicle': {'car': {'sedan': {'mini sedan': {}}}}}
result_dict is it.
Oh wow that's amazing. Thank you so much. Just wondering is there a way for regular code this? If not or take too much time that's fine
I don't get what regular code means. Can you explain more?
That means not using defaultdict
|
0

You can also do it manually with a recursive function. The idea:

  • Iterate over the element in the input list
  • Ignore element without id key
  • For element with an id in keys:
    • recursively search for this key in the out
    • add the element (if element already existing, the element is add in the recursive function, else after).
# Recursive search
def iterdictAdd(d, id, name):
    # For all element in the dict
    for k, v in d.items():
        # If key match -> add
        if k == id:
            d[id] = {**d[id], **{name: {}}}
            return True
        else:
            # Recursive call
            if isinstance(v, dict):
                if iterdictAdd(v, id, name):
                    return True
    return False


out = {}
# Iterate all dict
for dict_ in nested_list:
    # Ignore elements without "id" 
    if "id" in dict_:
        # Search and add this key
        if not iterdictAdd(out, dict_["id"], dict_["name"]):
            out[dict_["id"]] = {dict_["name"]: {}}

print(out)
# {'fruit': {
#     'apple': {},
#     'grape': {},
#     'pineapple': {}
# },
#  'vehicle': {
#     'car': {
#         'sedan': {}
#         }
#     }
# }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.