Python Forum
Looping to Create Nested Dictionary
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Looping to Create Nested Dictionary
#1
Hello!

My code is supposed to read lines from a file that contains several attributes: assignment number, assignment name, grade, total, and weight. Each line of the file has this format.

My code reads the lines and turns it into a nested dictionary. Currently, the code seems to be looping and overwriting the key values of the inner dictionary.

Here is the file it's reading from:
1 assignment_1 85 100 0.25
2 test_1 90 100 0.25
3 exam_1 95 100 0.5
The code should return a nested dictionary with the assignment name as the key, and the inner dictionary having the keys "number", "grade", "total", and "weight" for each line.
The correct output:

Output:
{'assignment_1': {'total': 100, 'number': 1, 'grade': 85, 'weight': 0.25}, 'test_1': {'total': 100, 'number': 2, 'grade': 90, 'weight': 0.25}, 'exam_1': {'total': 100, 'number': 3, 'grade': 95, 'weight': 0.5}}
Here is my code:
def reader(filename):
    file_reader = open(filename)
    results = []
    innerdict = {}
    outerdict = {}

    for line in file_reader:
        parts = line.split(" ")
        line_tuple = (int(parts[0]), parts[1], int(parts[2]), int(parts[3]), float(parts[4]))
        key = line_tuple[1]
        outerdict[key] = innerdict 

        innerdict["number"] = line_tuple[0]
        innerdict["grade"] = line_tuple[2]
        innerdict["total"] = line_tuple[3]
        innerdict["weight"] = line_tuple[4]
        
         

    return outerdict
It returns this output:
Output:
{'assignment_1': {'number': 3, 'weight': 0.5, 'total': 100, 'grade': 95}, 'test_1': {'number': 3, 'weight': 0.5, 'total': 100, 'grade': 95}, 'exam_1': {'number': 3, 'weight': 0.5, 'total': 100, 'grade': 95}}
I believe I am somewhat close, I just do not know how to preserve the values of the inner dictionary once it loops over a line.

Thank you for your help! I've been trying to figure this out for a few days and just wish to understand.
Reply
#2
This exercise is excellent to help the student understand that Python never copies objects but simply assign a pointer to the source object. So, when you execute outerdict[key] = innerdict, the content of outerdict[key] is not the hard values but simply a pointer to innerdict and when you modify innerdict a few statements below, of course the value of outerdict[key] is automatically modified.

You need to copy the hard values of the dictionary innerdict to make them independent of innerdict.
Reply
#3
Hm! Is it allowed dictionary comprehension?
Open the file and read it with the  readlines  method. This will create a list of the lines.
Create an empty dict  for the final result.
Iterate over the lines and for each line:
    split the line
    the_dict[splited_line[0]] = dict(zip(['number','grade','total','weight'], splited_line[1:]))
Done.

Of course, you will have to turn the digits from string to integers somewhere in the loop
"As they say in Mexico 'dosvidaniya'. That makes two vidaniyas."
https://freedns.afraid.org
Reply
#4
Squenson:
I vaguely understand! How could I copy and save the values of innerdict to make them independent of the dictionary?

Wavic:
I implemented the line you gave me and it worked for a second, but now it pulls up a ValueError. Here's the updated code:
def reader(filename):
    file_reader = open(filename)
    results = []
    innerdict = {}
    outerdict = {}

    for line in file_reader:
        parts = line.split(" ")
        parts = line.replace("\n", "")
        line_tuple = (int(parts[0]), parts[1], int(parts[2]), int(parts[3]), float(parts[4]))
        key = line_tuple[1]

     #   innerdict["number"] = line_tuple[0]
      #  innerdict["grade"] = line_tuple[2]
       # innerdict["total"] = line_tuple[3]
        #innerdict["weight"] = line_tuple[4]
        outerdict[parts[1]] = dict(zip(['number','grade','total','weight'], parts[1:]))
        
         
        #outerdict[key] = innerdict
    return outerdict
    
    file_reader.close()
And here's the error:
Error:
Traceback (most recent call last): File "Reader.py", line 81, in <module> print(reader("sample.cs1301")) File "Reader.py", line 57, in reader line_tuple = (int(parts[0]), parts[1], int(parts[2]), int(parts[3]), float(parts[4])) ValueError: invalid literal for int() with base 10: 'a' Command exited with non-zero status 1

(Dec-15-2017, 11:04 PM)wavic Wrote: Hm! Is it allowed dictionary comprehension?
Open the file and read it with the  readlines  method. This will create a list of the lines.
Create an empty dict  for the final result.
Iterate over the lines and for each line:
    split the line
    the_dict[splited_line[0]] = dict(zip(['number','grade','total','weight'], splited_line[1:]))
Done.

Of course, you will have to turn the digits from string to integers somewhere in the loop

Hi Wavic,
I've almost gotten it!
The only problem left with it is that 'number' in the inner dictionary has the wrong value.
The line of code you helped me with is from index 1, everything is right except for the value of 'number', which is given the
parts[1]
when that is the key for the larger dictionary.

Is there a way to get all of the indices excluding parts[1]?
Here's the code:
def reader(filename):
    file_reader = open(filename)
    results = []
    innerdict = {}
    outerdict = {}

    for line in file_reader:
        line = line.replace("\n", "")
        parts = line.split(" ")
        line_tuple = (int(parts[0]), parts[1], int(parts[2]), int(parts[3]), float(parts[4]))
        key = line_tuple[1]

     #   innerdict["number"] = line_tuple[0]
      #  innerdict["grade"] = line_tuple[2]
       # innerdict["total"] = line_tuple[3]
        #innerdict["weight"] = line_tuple[4]
        outerdict[parts[1]] = dict(zip(['number','grade','total','weight'], parts[1:]))
        
         
        #outerdict[key] = innerdict
    return outerdict
Here's the output. It's so close!
Output:
{'exam_1': {'grade': '95', 'total': '100', 'weight': '0.5', 'number': 'exam_1'}, 'test_1': {'grade': '90', 'total': '100', 'weight': '0.25', 'number': 'test_1'}, 'assignment_1': {'grade': '85', 'total': '100', 'weight': '0.25', 'number': 'assignment_1'}}
Reply
#5
Let's go back to your initial code and initial output
        outerdict[key] = innerdict 
 
        innerdict["number"] = line_tuple[0]
        innerdict["grade"] = line_tuple[2]
        innerdict["total"] = line_tuple[3]
        innerdict["weight"] = line_tuple[4]
From the output, we see that we have the right data of the third record, three times. This is "normal" because, as I said, the keys of outerdict point to innerdict and not its hard values.

Let's slightly reorganize the code by first populating innerdict, then assign it to the outerdict:
        innerdict["number"] = line_tuple[0]
        innerdict["grade"] = line_tuple[2]
        innerdict["total"] = line_tuple[3]
        innerdict["weight"] = line_tuple[4]

        outerdict[key] = innerdict 
And finally, how to tell python to copy the hard values of innerdict? You can use the following way:
        outerdict[key] = dict(innerdict) 
(Note: to be fully correct, this statement is OK for making a copy of a "simple" dictionary which itself doesn't contain any complex objects like a list or a dictionary. For such cases, do a research on "copy" and "deepcopy")
Reply
#6
Well, I didn't realize that the numbers are not the line numbers copied from your editor but data.  Smile
Do it by hand. You have to do some changes since the slicing is totally wrong.

# bellow this line add a for loop
outerdict[parts[1]] = dict(zip(['grade','total','weight'], parts[2:])) # get rid of the 'number' in zip() arguments. Indices are changed a bit

for line in file:
   items = line.split()
   outerdict[items[1]]['number'] = items[0]
"As they say in Mexico 'dosvidaniya'. That makes two vidaniyas."
https://freedns.afraid.org
Reply
#7
import re

out='''1 assignment_1 85 100 0.25
2 test_1 90 100 0.25
3 exam_1 95 100 0.5'''
innerdict = {}
outerdict = {}
parts_1 = out.split("\n")
for line in parts_1:
    
    parts = line.split(" ")
    line_tuple = (int(parts[0]), parts[1], int(parts[2]), int(parts[3]), float(parts[4]))
    key = line_tuple[1]
    outerdict[key] = dict(innerdict)
    outerdict[key]={'Total':'{0}'.format(parts[3]),'Number':'{0}'.format(parts[0]),'Grade':'{0}'.format(parts[2]),'Weight':'{0}'.format(parts[4])}
    
  
print(outerdict)

output:


Output:
{'assignment_1': {'Total': '100', 'Number': '1', 'Grade': '85', 'Weight': '0.25'}, 'test_1': {'Total': '100', 'Number': '2', 'Grade': '90', 'Weight': '0.25'}, 'exam_1': {'Total': '100', 'Number': '3', 'Grade': '95', 'Weight': '0.5'}}
You can try this.
Reply
#8
In short - don't init innerdict outside of the loop
outerdict = {}
for line in file:
    parts = line.split(" ")
    outerdict[parts[1]] = {}
......
As simple as that
Test everything in a Python shell (iPython, Azure Notebook, etc.)
  • Someone gave you an advice you liked? Test it - maybe the advice was actually bad.
  • Someone gave you an advice you think is bad? Test it before arguing - maybe it was good.
  • You posted a claim that something you did not test works? Be prepared to eat your hat.
Reply
#9
This is done as follows.
def reader(filename):
    with open(filename) as fin:
        txt = fin.read()
    lines=txt.strip().split('\n')
    outerdict={}
    for line in lines:
        lst=line.split()
        outerdict[lst[1]]={
            'number' : int(lst[0]),
            'grade'  : int(lst[2]),
            'total'  : int(lst[3]),
            'weight' : float(lst[4]),
        }
    return outerdict
Reply
#10
(Jun-22-2018, 08:47 AM)anickone Wrote: This is done as follows.
def reader(filename):
    with open(filename) as fin:
        txt = fin.read()
    lines=txt.strip().split('\n')
    outerdict={}
    for line in lines:
        lst=line.split()
        outerdict[lst[1]]={
            'number' : int(lst[0]),
            'grade'  : int(lst[2]),
            'total'  : int(lst[3]),
            'weight' : float(lst[4]),
        }
    return outerdict

While in-loop code makes the most sense of what I have seen above, separating file iteration and loop makes no sense.

BTW, there's str.splitlines method
Test everything in a Python shell (iPython, Azure Notebook, etc.)
  • Someone gave you an advice you liked? Test it - maybe the advice was actually bad.
  • Someone gave you an advice you think is bad? Test it before arguing - maybe it was good.
  • You posted a claim that something you did not test works? Be prepared to eat your hat.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  create empty sets for keys in a dictionary and add values to the set naughtysensei 1 2,512 Nov-03-2020, 08:32 AM
Last Post: DeaD_EyE
  how can i create a dictionary of dictionaries from a file Astone 2 2,253 Oct-26-2020, 02:40 PM
Last Post: DeaD_EyE
  nested looping with list cap510 2 1,905 Sep-10-2020, 04:51 AM
Last Post: cap510
  nested dictionary Maxime 3 3,576 Mar-14-2019, 07:19 AM
Last Post: perfringo
  Looping through a dictionary for every other value fad3r 15 9,501 Jan-25-2018, 07:12 PM
Last Post: j.crater

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020