Python Forum
Looping to Create Nested Dictionary - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: Homework (https://python-forum.io/forum-9.html)
+--- Thread: Looping to Create Nested Dictionary (/thread-6966.html)

Pages: 1 2


Looping to Create Nested Dictionary - gngu2691 - Dec-15-2017

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.


RE: Looping to Create Nested Dictionary - squenson - Dec-15-2017

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.


RE: Looping to Create Nested Dictionary - wavic - Dec-15-2017

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


RE: Looping to Create Nested Dictionary - gngu2691 - Dec-16-2017

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'}}



RE: Looping to Create Nested Dictionary - squenson - Dec-16-2017

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")


RE: Looping to Create Nested Dictionary - wavic - Dec-16-2017

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]



RE: Looping to Create Nested Dictionary - swarup19 - Jun-22-2018

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.


RE: Looping to Create Nested Dictionary - volcano63 - Jun-22-2018

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


RE: Looping to Create Nested Dictionary - anickone - Jun-22-2018

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



RE: Looping to Create Nested Dictionary - volcano63 - Jun-22-2018

(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