简体   繁体   中英

Converting a nested list of dictionary in Python

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

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)

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': {}
#         }
#     }
# }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM