Posts: 2
Threads: 1
Joined: Dec 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.
Posts: 87
Threads: 1
Joined: Dec 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.
Posts: 2,955
Threads: 48
Joined: Sep 2016
Dec-15-2017, 11:04 PM
(This post was last modified: Dec-15-2017, 11:04 PM by wavic.)
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
Posts: 2
Threads: 1
Joined: Dec 2017
Dec-16-2017, 12:00 AM
(This post was last modified: Dec-16-2017, 12:33 AM by gngu2691.)
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'}}
Posts: 87
Threads: 1
Joined: Dec 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")
Posts: 2,955
Threads: 48
Joined: Sep 2016
Dec-16-2017, 09:54 AM
(This post was last modified: Dec-16-2017, 09:55 AM by wavic.)
Well, I didn't realize that the numbers are not the line numbers copied from your editor but data.
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]
Posts: 1
Threads: 0
Joined: Jun 2018
Jun-22-2018, 04:56 AM
(This post was last modified: Jun-22-2018, 06:19 AM by buran.)
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.
Posts: 566
Threads: 10
Joined: Apr 2017
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.
Posts: 9
Threads: 2
Joined: Jun 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
Posts: 566
Threads: 10
Joined: Apr 2017
(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.
|