How to flatten disorganised dictionaries into list?

Viewed 97

I attempted to flatten a disorganized dictionary (that in turn was taken from a json file) to ease extracting info. Below is an example of how the dictionary is structured and my attempt at flattening it:

data = {'horse':{'speed':{"walk": 40, "run":50}}, 'dog':{'run':30}, 'human':{'gait':{'normal':{'run': 25, 'walk': 30}}}}

flat_dict = []
for items in list(data.items()):
    flat_list = []
    flat_list.append(items[0])
    try:
        for item in list(items[1].items())[0]:
            if type(item) is not dict: 
                flat_list.append(item)
            else:
                flat_list.append(list(item.keys())[0])
                flat_list.append(list(item.values())[0])
    except:
        flat_list.append(items[0])
    flat_dict.append(flat_list)

print(flat_dict)

However the above code does not flatten the entire dictionary and some information is lost, here's the output of the above code:

[['horse', 'speed', 'walk', 40], ['dog', 'run', 30], ['human', 'gait', 'normal', {'run': 25, 'walk': 30}]]

What I wanted was:

[['horse', 'speed', 'walk', 40, 'run', 50], ['dog', 'run', 30], ['human', 'gait', 'normal', 'run', 25, 'walk', 30]]

What do I do?

3 Answers

you can use a recursive approach with a list comprehension:

 def gen(d):
    if isinstance(d, dict):
        for k, v in d.items():
            yield k
            yield from gen(v)
    else:
        yield d 

[[k, *gen(v)] for k, v in data.items()]

output:

 [['horse', 'speed', 'walk', 40, 'run', 50],
 ['dog', 'run', 30],
 ['human', 'gait', 'normal', 'run', 25, 'walk', 30]]

As you don't know the structure inside the dict you cannot use simple loops to handle each case, you need to use recursion, I'd suggest an utility method to flatten whatever structure recursivly, then make use it to make arrays of [key, flatten(values)]

def flatten(values) -> list:
    if isinstance(values, list):
        return [v for value in values for v in flatten(value)]
    if isinstance(values, dict):
        return [*values.keys(), *flatten(list(values.values()))]
    return [values]


def flatten_dict(values: dict) -> list:
    return [[key, *flatten(value)] for key, value in values.items()]


if __name__ == '__main__':
    # ['foo']
    print(flatten('foo'))

    # ['foo', 'bar', 'uio', 1, 2, 3, 'k1', 'k2', 'v1', 'kk1', '9', 5, 9, 8, 7]
    print(flatten(['foo', ['bar', 'uio', [1, 2, 3]], {'k1': 'v1', 'k2': {'kk1': ['9', 5, 9, 8, 7, ]}}]))

    data = {'horse': {'speed': {"walk": 40, "run": 50}}, 'dog': {'run': 30},
            'human': {'gait': {'normal': {'run': 25, 'walk': 30}}}}

    # [['horse', 'speed', 'walk', 'run', 40, 50], ['dog', 'run', 30], ['human', 'gait', 'normal', 'run', 'walk', 25, 30]]
    print(flatten_dict(data))

Answered as asked:

data = {
    'horse': {
        'speed': {
            "walk": 40, "run": 50}},
    'dog': {
        'run': 30},
    'human': {
        'gait': {
            'normal': {
                'run': 25, 'walk': 30}}}}


def my_flatten(ddict, mylist):
    for k, v in ddict.items():
        if isinstance(v, dict):
            mylist.append(k)
            my_flatten(v, mylist)
        else:
            mylist.extend([k, v])
    return mylist


flist = [my_flatten(v, [k]) for k, v in data.items()]
print(flist)
Related