Michael Tan
Michael Tan

Reputation: 81

Python: How to print a nested dictionary as a complex table with variable rows?

Say I have this nested dictionary:

d = {'Ben': {'wo_mana': ['Strength = 1.10', 'Speed = 1.50'], 'wi_mana': ['Int = 1.20'], 'Skill': 'true', 'Magic': 'false'},
     'Tom': {'wi_mana': ['Int = 1.40'], 'wo_mana': ['Agility = 1.60'], 'Skill': 'true', 'Magic': 'true'}}

The keys and values are not defined by myself, they are actually data extracted from a file.

The number of values for Skill and Magic is fixed, which is 1.

However, the number of values for wo_mana and wi_mana is unfixed, which can be 1, 2, 3, 4 etc.

In the example above, wo_mana have 2 values for Ben.

My goal is to print it in a table (separated with a tab) and when opened in Excel, it looks like this:

Name Skill Magic wo_mana          wi_mana
Ben  true  false Strength = 1.10  Int = 1.20
                 Speed = 1.50
Tom  true  true  Agility = 1.60   Int = 1.40

Note that for each people, the wo_mana and wi_mana columns can have multiple rows.

I have tried to use the csv module, my code:

import csv, itertools

header = ['Name', 'Skill', 'Magic', 'wo_mana', 'wi_mana']

with open('output.csv', 'w') as f:
    w = csv.DictWriter(f, header)
    w.writeheader()
    for key, val in sorted(d.items()):
        row = {'Name': key}
        row.update(val)
        w.writerow(row)

My output:

Name,Skill,Magic,wo_mana,wi_mana
Ben,true,false,"['Strength = 1.10', 'Speed = 1.50']",['Int = 1.20']
Tom,true,true,['Agility = 1.60'],['Int = 1.40']

So it looks like it's not suitable to print the multiple rows part using csv

So maybe I have to use string formatting? Does anyone know how to print the table like that? Any help would be greatly appreciated.

Upvotes: 1

Views: 1779

Answers (1)

Devi Prasad Khatua
Devi Prasad Khatua

Reputation: 1235

Here you go(edited):

import csv, itertools
d = {'Ben': {'wo_mana': ['Strength = 1.10'], 'wi_mana': ['Int = 1.20'], 'Skill': 'true', 'Magic': 'false'},
     'Tom': {'wi_mana': ['Int = 1.40'], 'wo_mana': ['Agility = 1.60'], 'Skill': 'true', 'Magic': 'true'}}


header = ['Name', 'Skill', 'Magic', 'wo_mana', 'wi_mana']

with open('output.csv', 'w') as f:
    w = csv.DictWriter(f, header)
    w.writeheader()
    for key, val in sorted(d.items()):
        row = {'Name': key}
        # print val


        wo_mana = [item for item in val['wo_mana']]
        wi_mana = [item for item in val['wi_mana']]
        val['wo_mana'] = wo_mana[0]
        val['wi_mana'] = wi_mana[0]    

        row.update(val)
        w.writerow(row)
        dict_list = []
        if len(wi_mana) > len(wo_mana):

            for index, item in enumerate(wo_mana):
                dict_ = {}
                dict_['wi_mana'] = wi_mana[index]
                dict_['wo_mana'] = wo_mana[index]
                # print dict_

                dict_list.append(dict_)
            [dict_list.append({'wi_mana': item}) for item in wi_mana[len(wo_mana):]]
        else:

            for index, item in enumerate(wi_mana):
                dict_ = {}
                dict_['wi_mana'] = wi_mana[index]
                dict_['wo_mana'] = wo_mana[index]
                # print dict_
                dict_list.append(dict_)
            [dict_list.append({'wo_mana': item}) for item in wo_mana[len(wi_mana):]]
        # print dict_list
        if len(dict_list) > 1:
            [w.writerow(dict_) for dict_ in dict_list[1:]]

This works ! Let me know.

Edit: According to the question:

However, the number of values for wo_mana and wi_mana is unfixed, which can be 1, 2, 3, 4 etc.

So I had assumed there would be no KeyError. According to the modified statements in the comments here is the solution:

It's not tested properly to various conditions and based on what I understood from the comments it works fine, let me know if any problem:

import csv, itertools
d = {'Ben': {'Skill': 'true', 'Magic': 'false'},
     'Tom': {'wo_mana': ['Strength = 1.10'], 'wi_mana': ['Int = 1.20', 'O_o'], 'mana': ['Int = 1.20', 'dsadas', 'whatever', '-_-'], 'Skill': 'true', 'Magic': 'true'}}


header = ['Name', 'Skill', 'Magic', 'wo_mana', 'wi_mana', 'mana']

with open('output.csv', 'w') as f:
    w = csv.DictWriter(f, header)
    w.writeheader()
    for key, val in sorted(d.items()):
        row = {'Name': key}
        # print val
        row.update(val)
        dictionary_containing_list = {key: val[key] for key in val if type(val[key])==list}
        if dictionary_containing_list:
            max_length = len(max(dictionary_containing_list.iteritems(), key=lambda x: len(x[1]))[1])
            dict_list = []
            for i in range(max_length):
                dict_ = {}
                for key, val_list in dictionary_containing_list.iteritems():
                    try:
                        dict_[key] = val_list[i]
                    except IndexError:
                        pass
                dict_list.append(dict_)
                row.update(dict_list[0])
            print dict_list
        w.writerow(row)
        if dictionary_containing_list:
            [w.writerow(dict_) for dict_ in dict_list[1:]]

The solution is highly generic and you can add as many numbers of manas/ columns and it will work. Remember to append the exact column name to the header list!

Upvotes: 1

Related Questions