loop through nested dictionary in python and display key value pair - python

i am a beginner in python i and i came up this problem and i cant seem to solve it.I have the following dictionary
stats = {1: {"Player": "Derrick Henry", "yards": 870, "TD": 9}, 2: {"Player": "Nick Chubb", "Yards": 841, "TD": 10}, 3: {"Player": "Saquon Barkley", "Yards": 779, "TD": 5}}
I want to loop through a dictionary and display the values as shown below
Player1
Player=Derrick Henry
yards=870
TD=9
player 2
Player=Nnikki Chubb
yards=770
TD=10
player3
Player=Nikki Chubb
yards=770
TD=10
i tried the following code
stats = {1: {"Player": "Derrick Henry", "Yards": 870, "TD": 9}, 2: {"Player": "Nick Chubb", "Yards": 841, "TD": 10}, 3: {"Player": "Saquon Barkley", "Yards": 779, "TD": 5}}
for key, value in stats.items():
print(value)
for x, y,z in value.items():
print("Player {}".format(key))
#IF Player
if x == "Player":
print("Player = {}".format(x))
#IF YARDS
if y == "Yards":
print("Yards = {}".format(y))
#IF YARDS
if z == "TD":
print("yards = {}".format(y))
Any help will be appreciated.Thank you

Don't you see here the useless logic : if a variable is something, you write manualmy that thing in a string, just use it directly
if x == "Player":
print("Player = {}".format(x))
if y == "Yards":
print("Yards = {}".format(y))
if z == "TD":
print("TD = {}".format(y))
Also you did well use .items first time, but misuses it the second time, it iterate over pair, so it'll always yields 2 variable , not 3
for key, props in stats.items():
print(f"Player{key}")
for prop_key, prop_value in props.items():
print(f"{prop_key}={prop_value}")

You kinda haven't really decided yet, if you want to iterate over the nested dict or not. To iterate over it, check azro's answer. But what you are attempting is not iterating, so you can just write:
print("Player = {}".format(value["Player"]))
print("Yards = {}".format(value["Yards"]))
print("TD = {}".format(value["TD"]))
Or, as the print statements are all the same, you could loop over the keys you want to print:
for key in ["Player", "Yards", "TD"]:
print("{} = {}".format(key, value[key])

Related

Validate a record in a dict list, if found, update, if not, insert (Python)

I have a minor problem because of my understanding of how this works. I have a function to play the dice game, then send the results in a list to another function where valid the dice results, the list takes the name of the player and the values ​​of the dice, it looks like this:
['John', 1, 2, 3, 3, 3]
Then I'm trying to save the record in a dict, to later save it in a new list, where all the information of the players will be, it will look like this:
[
{
'Name': 'luis',
'Dice': (1, 2, 3, 3, 3),
'Bet': 500
}, {
'Name': 'andrew',
'Dice': (2, 2, 2, 1, 2),
'Bet': 500
}
]
But I don't know how to do the following:
If the list is empty, without player dictionaries, save the first one based on a previous validation.
Whose validation is that it checks if the "key" Name already has the "value" of the player's name, if so, it does not insert a new one, like the first point, but it updates the value of the "key" dice to the new values ​​of the dice you receive.
Here is my full code:
import os
import random as r
from colorama import *
os.system('cls')
player_data = []
def validate_values(list_values):
dice_player_values = list_values[1], list_values[2], list_values[3], list_values[4], list_values[5]
player = {
"Name": list_values[0],
"Dice": dice_player_values,
"Bet": 500
}
if(1 not in list_values):
print(Fore.RED + Style.BRIGHT + list_values[0] + ", next, don't have enough look." + Style.RESET_ALL)
os.system('pause')
play_dice()
else:
# player_data.append(player)
# next((item for item in player_data if item["name"] == list_values[0]), None)
for dato in player_data:
if(dato["Name"] != list_values[0]):
player_data.append(player)
continue
else:
dato["Name"]["Dice"] = dice_player_values
contar_unos = list_values.count(1)
print("It find's: ", str(contar_unos) , ", one.")
print(player_data)
os.system('pause')
play_dice()
def play_dice():
os.system('cls')
dice_values = []
dice_values.clear()
print(Fore.GREEN + "What's your name? : " + Style.RESET_ALL)
player_name = input().lower()
dice_values.append(player_name)
dice_one = r.randint(1,3)
dice_values.append(dice_one)
dice_two = r.randint(1,3)
dice_values.append(dice_two)
dice_three = r.randint(1,3)
dice_values.append(dice_three)
dice_four = r.randint(1,6)
dice_values.append(dice_four)
dice_five = r.randint(1,6)
dice_values.append(dice_five)
print(dice_values)
os.system('pause')
validate_values(dice_values)
return dice_values
dice = play_dice()
I think your error is where you are checking if there's already a player with a certain name in the list:
for dato in player_data:
if(dato["Name"] != list_values[0]):
player_data.append(player)
continue
This is only checking if the current item in the list has a person with that specific name. You need to check them all at once before you do anything. So you could do something like this:
newPlayer = false
for dato in player_data:
if dato['Name'] == list_values[0]:
dato['Dice'] = dice_player_values
newPlayer = true
break
if newPlayer == true:
player_data.append(player)

Python: JSON, Problem with list of dictionaries, certain value into my dictionary

So, I have been thinking of this for a long time now, but can't seem to get it right. So I have to use a JSON file to make a dictionary where I get the keys: 'userIds' and the value 'completed' tasks in a dictionary. The best I got was the answer: {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 90}, with this code under:
import requests
response1 = requests.get("https://jsonplaceholder.typicode.com/todos")
data1 = response1.json()
dict1 = {}
keys = []
values = []
for user in data1:
if user not in keys or values:
keys.append(user['userId'])
values.append(0)
for key, value in zip(keys, values):
dict1[key] = value
for user in data1:
if user['completed'] == True:
dict1[key] += 1
print(dict1)
but I feel like this next code would be closer, but I can't figure out how to get it to work
import requests
response1 = requests.get("https://jsonplaceholder.typicode.com/todos")
data1 = response1.json()
dict1 = {}
keys = []
values = []
for user in data1:
if user not in keys or values:
keys.append(user['userId'])
values.append(0)
for key, value in zip(keys, values):
dict1[key] = value
for key, value in data1.items():
if user['completed'] == True:
dict1[key].update += 1
print(dict1)
After this, the output is just
" line 24, in
for key, value in data1.items():
AttributeError: 'list' object has no attribute 'items'",
And I do get why, I don't jsut know how to continue from here.
Would really appreciate anyones help, with this obnoxious task.
can u try this ?
import requests
response1 = requests.get("https://jsonplaceholder.typicode.com/todos")
data1 = response1.json()
dict1 = {}
keys = []
values = []
for user in data1:
if user not in keys or values:
keys.append(user['userId'])
values.append(0)
for key, value in zip(keys, values):
dict1[key] = value
print(data1)
for x in data1:
for key, value in x.items():
if key =="completed" :
if value == True:
dict1[x["userId"]] += 1
print(dict1)
Try with this approach:
import json
import requests
response1 = requests.get("https://jsonplaceholder.typicode.com/todos")
data1 = response1.json()
dict1 = {}
for user in data1:
uid = user['userId']
if user['completed']:
dict1[uid] = dict1.get(uid, 0) + 1
elif uid not in dict1:
dict1[uid] = 0
print(dict1)
print(json.dumps(dict1, indent=2))
If needed, you can also simplify the above logic using defaultdict, and leverage the fact that bool is a subclass of int:
from collections import defaultdict
dict1 = defaultdict(int)
for user in data1:
dict1[user['userId']] += user['completed']
Output:
{1: 11, 2: 8, 3: 7, 4: 6, 5: 12, 6: 6, 7: 9, 8: 11, 9: 8, 10: 12}
{
"1": 11,
"2": 8,
"3": 7,
"4": 6,
"5": 12,
"6": 6,
"7": 9,
"8": 11,
"9": 8,
"10": 12
}

Limit number of items / length of json for logging

I am working on an API that returns JSON. I am logging my responses, and sometimes the JSON is just absurdly long and basically clogs my log files. Is there a neat way to reduce the length of a JSON, purely for visually logging the data? (not in effect in production)
The basic approach is to reduce arrays over a length of 5 to [first 2, "...", last 2], and dictionaries with more than 4 items to {first 4, "..." : "..."}
The code below is ugly. I am aware that it should be a recursive solution that reduces the items in the same way for a JSON of arbitrary depth - it currently only does so for depth 2.
def log_reducer(response_log):
original_response_log = response_log
try:
if type(response_log) == dict:
if len(response_log) >= 4: # {123456}
response_log = dict(list(response_log.items())[:4])
response_log.update({"...": "..."}) # {1234...}
for key, value in response_log.items():
if type(value) == list:
if len(value) >= 5: # {key:[123456]}
new_item = value[:2] + ['...'] + value[-2:] # {[12...56]}
response_log.update({key: new_item})
if type(value) == dict:
if len(value) >= 4: # {key:{123456}}
reduced_dict = dict(list(value.items())[:4])
reduced_dict.update({"...": "..."})
response_log.update({key: reduced_dict}) # {{1234...}}
elif type(response_log) == list:
if len(response_log) >= 5: # [123456]
response_log = response_log[:2] + ['...'] + response_log[-2:] # [12...56]
for inner_item in response_log:
if type(inner_item) == list:
if len(inner_item) >= 5: # [[123456]]
reduced_list = inner_item[:2] + ['...'] + inner_item[-2:] # [[12...56]]
response_log.remove(inner_item)
response_log.append(reduced_list)
if type(inner_item) == dict:
if len(inner_item) >= 4: # [{123456}]
reduced_dict = dict(list(inner_item.items())[:4])
reduced_dict.update({"...": "..."}) # [{1234...}]
response_log.remove(inner_item)
response_log.append(reduced_dict)
except Exception as e:
return original_response_log
return response_log
The returned response_log is then logged with logger.info(str(response_log))
As you can see, the fact that there can be either arrays or dictionaries at every level makes this task a little more complex, and I am struggling to find a library or code snipped of any kind which would simplify this. If anyone wants to give it a shot, I would appreciate it a lot.
you can use a test JSON like this to see it in effect:
test_json = {"works": [1, 2, 3, 4, 5, 6],
"not_affected": [{"1": "1", "2": "2", "3": "3", "4": "4", "5": "5"}],
"1": "1", "2": "2", "3": "3",
"removed": "removed"
}
print("original", test_json)
reduced_log = log_reducer(test_json)
print("reduced", reduced_log)
print("original", test_json)
reduced_log = log_reducer([test_json]) # <- increases nesting depth
print("reduced", reduced_log)
This answer uses #calceamenta's idea, but implements the actual cutting-down logic:
def recursive_reduce(obj):
if isinstance(obj, (float, str, int, bool, type(None))):
return obj
if isinstance(obj, dict):
keys = list(sorted(obj))
obj['...'] = '...'
if len(keys) > 5:
new_keys = keys[:2] + ["..."] + keys[-2:]
else:
new_keys = keys
new_dict = {x:obj[x] for x in new_keys}
for k, v in new_dict.items():
new_dict[k] = recursive_reduce(v)
return new_dict
if isinstance(obj, list):
if len(obj) > 5:
new_list = obj[:2] + ["..."] + obj[-2:]
else:
new_list = obj
for i, v in enumerate(new_list):
new_list[i] = recursive_reduce(v)
return new_list
return str(obj)
test_json = {"works": [1, 2, 3, 4, 5, 6],
"not_affected": [{"1": "1", "2": "2", "3": "3", "4": "4", "5": "5"}],
"1": "1", "2": "2", "3": "3",
"removed": "removed"
}
print("original", test_json)
reduced_log = recursive_reduce(test_json)
print("reduced", reduced_log)
Output:
original {'works': [1, 2, 3, 4, 5, 6], 'not_affected': [{'1': '1', '2': '2', '3': '3', '4': '4', '5': '5'}], '1': '1', '2': '2', '3': '3', 'removed': 'removed'}
reduced {'1': '1', '2': '2', '...': '...', 'removed': 'removed', 'works': [1, 2, '...', 5, 6]}
Hope this helps :)
You can overwrite the string representation of dicts and lists in python using the def __str__(): method. Using this just recursively call the print function on all elements. It can have a simple boilerplate like this:
def custom_print(obj):
log_str = ''
if type(obj) == list:
for item in obj:
log_str += custom_print(item)
elif type(obj) == dict:
for k, item in obj.items():
custom_print(item)
Use this custom log function to print into your log file as per your log file format.

Change money with python dynamic programming

Here are two programs for change money problem. The first one is only a recursion program that get all combinations and the second one is using dynamic programming. HOWEVER, i get into trouble when I am working on second one. It is supposed to be faster than the first one, but my program runs FOREVER to do it. I am pretty sure that I am using the dynamic programming, but i don't know what's the problem in it?
Notes: Total is the money going to be changed, units is a list with different values and stored is a dictionary to store the value of a step.
First:
def changeMoney(total, units):
if ( total == 0 ):
return [{}]
elif ( total < 0 ):
return []
else:
n = len(units)
ret = []
for i in range(0,n):
sols = changeMoney(total-units[i],units[i:n])
for sol in sols:
if ( units[i] in sol ):
sol[units[i]] += 1
else:
sol[units[i]] = 1
ret.append(sol)
return ret
print(dpChangeMoney(300,[100,50,20,10,5,2,1],{}))
Second:
import copy
def dpChangeMoney(total, units, stored):
key = ".".join(map(str,[total] + units))
if key in stored:
return stored[key]
else:
if ( total == 0 ):
return [{}]
elif ( total < 0 ):
return []
else:
n = len(units)
for i in range(0,n):
sols = copy.deepcopy(dpChangeMoney(total-
units[i],units[i:n], stored))
for sol in sols:
if ( units[i] in sol ):
sol[units[i]] += 1
else:
sol[units[i]] = 1
if key in stored:
if sol not in stored[key]:
stored[key] += [sol]
else:
stored[key] = [sol]
return stored[key]
print(dpChangeMoney(300,[100,50,20,10,5,2,1],{}))
Here's a much faster way to do this:
def dpChangeMoney(total, units, stored, min_ix=0):
if total < 0:
return []
if total == 0:
return [{}]
if min_ix == len(units):
return []
key = (total, min_ix)
if key in stored:
return stored[key]
sol_list = []
u = units[min_ix]
for c in range(total // u + 1):
sols = dpChangeMoney(total - c*u, units, stored, min_ix + 1)
for sol in sols:
if c > 0:
sol2 = sol.copy()
sol2[u] = c
else:
sol2 = sol
sol_list.append(sol2)
stored[key] = sol_list
return sol_list
If invoked as follows, it prints the number of solutions for the specified case:
print(len(dpChangeMoney(300, [100,50,20,10,5,2,1], {})))
The result is:
466800
On my system this took well under a second to run. (Of course, you could print the actual solutions, but there are a lot!)
To see the actual solutions for a total of 10:
print(dpChangeMoney(10, [100,50,20,10,5,2,1], {}))
The result is:
[{1: 10}, {1: 8, 2: 1}, {1: 6, 2: 2}, {1: 4, 2: 3}, {1: 2, 2: 4}, {2: 5}, {1: 5, 5: 1}, {1: 3, 2: 1, 5: 1}, {1: 1, 2: 2, 5: 1}, {5: 2}, {10: 1}]
i just figure out what is the problem in my algorithm. I will update a much faster algorithm after the due date. Thanks for your suggestions and instructions. E

Rewrite the code without using objects in python

When not using objects i am finding it difficult to store and parse through the data. Looking for ways where i can shorten the code using generator comprehension.
for a problem where input to the program code is as below
Courses
TRAN~Transfiguration~1~2011-2012~Minerva McGonagall
CHAR~Charms~1~2011-2012~Filius Flitwick
Students
SLY2301~Hannah Abbott
SLY2302~Euan Abercrombie
SLY2303~Stewart Ackerley
SLY2304~Bertram Aubrey
SLY2305~Avery
SLY2306~Malcolm Baddock
SLY2307~Marcus Belby
SLY2308~Katie Bell
SLY2309~Sirius Orion Black
Grades
TRAN~1~2011-2012~SLY2301~AB
TRAN~1~2011-2012~SLY2302~B
TRAN~1~2011-2012~SLY2303~B
TRAN~1~2011-2012~SLY2305~A
TRAN~1~2011-2012~SLY2306~BC
TRAN~1~2011-2012~SLY2308~A
TRAN~1~2011-2012~SLY2309~AB
CHAR~1~2011-2012~SLY2301~A
CHAR~1~2011-2012~SLY2302~BC
CHAR~1~2011-2012~SLY2303~B
CHAR~1~2011-2012~SLY2305~BC
CHAR~1~2011-2012~SLY2306~C
CHAR~1~2011-2012~SLY2307~B
CHAR~1~2011-2012~SLY2308~AB
EndOfInput
Expected Output
SLY2301~Hannah Abbott~9.5
SLY2302~Euan Abercrombie~7.5
SLY2303~Stewart Ackerley~8.0
SLY2304~Bertram Aubrey~0
SLY2305~Avery~8.5
SLY2306~Malcolm Baddock~6.5
SLY2307~Marcus Belby~8.0
SLY2308~Katie Bell~9.5
SLY2309~Sirius Orion Black~9.0
I managed to solve it using objects but is there any other way to write the code without using objects?
import sys
from Courses.Courses import Course
from Students.Students import Student
from Grades.Grades import Grade
courses = []
students = []
grades = []
gradeDict = {'A':10,'AB':9,'B':8,'BC':7,'C':6,'CD':5,'D':4}
courseCodeDict = {}
def readInput():
isSectionStart=True
while True:
# Reading data from console
input_var = raw_input()
if "EndOfInput" != input_var:
if input_var in "Courses Students Grades":
section=input_var
isSectionStart=True
else:
isSectionStart=False
if not isSectionStart:
extractDataFromRawData(input_var,section)
else:
break;
#printData(courses,students,grades)
calculateGradeAverage(grades,courses)
def calculateGradeAverage(grades,courses):
print("Calculating Average now...")
gradeRollNumberDict={}
courseGradeDict={}
gradesSet = {}
for course in courses:
courseCodeDict.update({course.course_code : 1})
for grade in grades:
if gradeRollNumberDict.get(grade.roll_number) == None:
grade.totalGradePoint = grade.grade
gradeRollNumberDict.update({grade.roll_number : grade.totalGradePoint})
else:
grade.totalGradePoint= grade.grade + gradeRollNumberDict.get(grade.roll_number)
gradeRollNumberDict.update({grade.roll_number : grade.totalGradePoint})
if courseGradeDict.get(grade.roll_number) == None:
grade.totalCourseTaken = courseCodeDict.get(grade.course_code)
courseGradeDict.update({grade.roll_number : courseCodeDict.get(grade.course_code)})
else:
grade.totalCourseTaken= courseCodeDict.get(grade.course_code) + courseGradeDict.get(grade.roll_number)
courseGradeDict.update({grade.roll_number : grade.totalCourseTaken})
for grade in grades:
grade.avgGrade = grade.totalGradePoint/grade.totalCourseTaken
grade.avgGrade = round(grade.avgGrade)
seenGrades = set()
uniqueGrades = []
grades.reverse()
for grade in grades:
if grade.roll_number not in seenGrades:
uniqueGrades.append(grade)
seenGrades.add(grade.roll_number)
#uniqueGrades.reverse()
for a in uniqueGrades:
print(a.roll_number)
#print(uniqueGrades)
grades=uniqueGrades
grades.sort(key=lambda grade:grade.roll_number)
for grade in grades:
print("RollNumber: {0} \t Total CourseTaken: {1} \t Total Grade Point: {2} \t Avg Grade: {3}".format(grade.roll_number,grade.totalCourseTaken,grade.totalGradePoint,grade.avgGrade))
def extractDataFromRawData(input_data,section):
if "Courses" == section:
courses.append(createCourseObject(input_data))
elif "Students" == section:
students.append(createStudentObject(input_data))
elif "Grades" == section:
grades.append(createGradeObject(input_data))
else:
print("Invalid input!!! Exiting the system...")
sys.exit()
def createCourseObject(input_data):
courseInputData = input_data.split("~")
course = Course(courseInputData[0],courseInputData[1],courseInputData[2],courseInputData[3],courseInputData[4])
return course
def createStudentObject(input_data):
studentInputData = input_data.split("~")
student = Student(studentInputData[0],studentInputData[1])
return student
def createGradeObject(input_data):
gradeInputData = input_data.split("~")
grade = Grade(gradeInputData[0],gradeInputData[1],gradeInputData[2],gradeInputData[3],gradeDict[gradeInputData[4]])
return grade
def printData(courses,students,grades):
printObject(courses,"Courses")
printObject(students,"Students")
printObject(grades,"Grades")
def printObject(list,object):
print("Printing %s"%object)
for data in list:
print(data)
if __name__ == '__main__':
readInput()
Code:
from collections import OrderedDict
from pprint import pprint as pp
SEPARATOR = "~"
GRADE_DICT = {
"A": 10,
"AB": 9,
"B": 8,
"BC": 7,
"C": 6,
"CD": 5,
"D": 4
}
def read_input_from_file(file_name="input.txt"):
course_list= list()
student_list = list()
grade_list = list()
section_map = {
"Courses": course_list,
"Students": student_list,
"Grades": grade_list,
}
with open(file_name) as f:
current_item = None
for line in f:
line = line.strip()
if line in section_map:
current_item = section_map[line]
elif line == "EndOfInput":
break
elif current_item is not None:
current_item.append(line)
else:
print("Ignoring line: {}".format(line))
return course_list, student_list, grade_list
def convert_names(name_list):
ret = OrderedDict()
for element in name_list:
id, name = element.split(SEPARATOR)
ret[id] = name
return ret
def convert_grades(grade_list):
ret = dict()
for element in grade_list:
course_id, student_id, grade_id = element.rsplit(SEPARATOR, 2)
ret.setdefault(student_id, dict())[course_id] = grade_id
return ret
def main():
course_list, student_list, grade_list = read_input_from_file()
student_dict = convert_names(student_list)
print("\n[SECTION 0]: Student IDs and names:\n")
pp(student_dict)
exam_stat_dict = convert_grades(grade_list)
print("\n[SECTION 1]: Grades organized by students and courses:\n")
pp(exam_stat_dict)
print("\n[SECTION 2]: Final Grades:\n")
for student_id in student_dict:
if student_id in exam_stat_dict:
grade_dict = exam_stat_dict[student_id]
grades_sum = sum([GRADE_DICT.get(item, 0) for item in grade_dict.values()])
print(SEPARATOR.join([student_id, student_dict[student_id], str(grades_sum/len(grade_dict))]))
else:
print(SEPARATOR.join([student_id, student_dict.get(student_id), "0.0"]))
if __name__ == "__main__":
main()
Output (I'm placing it before the Notes, since I'm going to refer to it from there):
(py35x64_test) c:\Work\Dev\StackOverflow\q45987148>python a.py
[SECTION 0]: Student IDs and names:
OrderedDict([('SLY2301', 'Hannah Abbott'),
('SLY2302', 'Euan Abercrombie'),
('SLY2303', 'Stewart Ackerley'),
('SLY2304', 'Bertram Aubrey'),
('SLY2305', 'Avery'),
('SLY2306', 'Malcolm Baddock'),
('SLY2307', 'Marcus Belby'),
('SLY2308', 'Katie Bell'),
('SLY2309', 'Sirius Orion Black')])
[SECTION 1]: Grades organized by students and courses:
{'SLY2301': {'CHAR~1~2011-2012': 'A', 'TRAN~1~2011-2012': 'AB'},
'SLY2302': {'CHAR~1~2011-2012': 'BC', 'TRAN~1~2011-2012': 'B'},
'SLY2303': {'CHAR~1~2011-2012': 'B', 'TRAN~1~2011-2012': 'B'},
'SLY2305': {'CHAR~1~2011-2012': 'BC', 'TRAN~1~2011-2012': 'A'},
'SLY2306': {'CHAR~1~2011-2012': 'C', 'TRAN~1~2011-2012': 'BC'},
'SLY2307': {'CHAR~1~2011-2012': 'B'},
'SLY2308': {'CHAR~1~2011-2012': 'AB', 'TRAN~1~2011-2012': 'A'},
'SLY2309': {'TRAN~1~2011-2012': 'AB'}}
[SECTION 2]: Final Grades:
SLY2301~Hannah Abbott~9.5
SLY2302~Euan Abercrombie~7.5
SLY2303~Stewart Ackerley~8.0
SLY2304~Bertram Aubrey~0.0
SLY2305~Avery~8.5
SLY2306~Malcolm Baddock~6.5
SLY2307~Marcus Belby~8.0
SLY2308~Katie Bell~9.5
SLY2309~Sirius Orion Black~9.0
Notes:
This is a "slightly" modified version of your code, that only uses stuff from Python standard library
Code explanation:
read_input_from_file (since it's only a helper function, I'm not going to insist much on it):
I saved the input (copy/paste) in a file (called it input.txt), and every time the program runs, it loads the data from there (the reason is obvious)
It (populates and) returns 3 lists (curses, students and grades from your code)
convert_names:
Converts every student name entry (as given in input) into a dictionary*: {id: name} (e.g. "SLY2301~Hannah Abbott" -> {"SLY2301": "Hannah Abbott"}) - the key will be id
*Since in a regular Python dictionary ([Python]: Mapping Types — dict) the keys are ordered by their hash (the hash function can change between Python versions), there's almost 100% chance that the dictionary elements won't be stored in the order they were inserted (as an example you could type in the Python console {1:2, 0:1} and you'll see that it will output {0: 1, 1: 2}), I'm using [Python]: class collections.OrderedDict([items]) which ensures the key order
The return value can be seen in program output (SECTION 0)
convert_grades:
This is where (most of) the magic takes place
Converts every grade entry (as given in input) in a dictionary: {student_id : {course_id: grade_id}} (the last 2 values are aggregated in an inner dictionary; e.g. "TRAN~1~2011-2012~SLY2301~AB" -> {"SLY2301": {"TRAN~1~2011-2012": "AB"}}). For that, I'm using [Python]: str.rsplit(sep=None, maxsplit=-1) with a maxsplit value of 2, as I don't care about the ~s in TRAN~1~2011-2012
If a student_id is present more than once (was to more than 1 course exam), I am just adding the course_id and grade_id in the inner dictionary (this is where [Python]: setdefault(key[, default]) comes into play)
The return value can be seen in program output (SECTION 1)
main:
The program main function. Here, I'm making use of the other functions and display the final data in a proper manner to the user (SECTION 2)
If there was a student that wasn't at any exam, like Bertram Aubrey (the id is not present in the exam statistics dictionary), I just print the id, name and 0.0
Otherwise, I calculate the arithmetic average from the grades in the inner dictionary (I am using [Python]: list Comprehensions to convert the grades into actual numbers, [Python]: sum(iterable[, start]) to sum the grades, then I divide the total by the number of inner dictionary keys) and display it, together with the id and name
The code runs with Python3 and Python2
#EDIT0:
Adding read_input function (read_input_from_file with minimum and trivial modifications) to read input from keyboard:
def read_input():
course_list= list()
student_list = list()
grade_list = list()
section_map = {
"Courses": course_list,
"Students": student_list,
"Grades": grade_list,
}
current_item = None
while(1):
line = input()
if line in section_map:
current_item = section_map[line]
elif line == "EndOfInput":
break
elif current_item is not None:
current_item.append(line)
else:
print("Ignoring line: {}".format(line))
return course_list, student_list, grade_list
Notes:
In order for this function to work with Python2, this code should be added at the beginning of the file:
import sys
if sys.version_info.major < 3:
input = raw_input
You can also use the input.txt file to test the code with large datasets (like provided in the question, without having to manually type all the data) like this:
python a.py < input.txt
#CristiFati using your code i managed to tweak to solve my problem as below.
from collections import OrderedDict
import sys
SEPARATOR = "~"
GRADE_DICT = {
"A": 10,
"AB": 9,
"B": 8,
"BC": 7,
"C": 6,
"CD": 5,
"D": 4
}
def read_input_from_file():
course_list= list()
student_list = list()
grade_list = list()
section_map = {
"Courses": course_list,
"Students": student_list,
"Grades": grade_list,
}
current_item = None
while True:
line = input()
#line = line.strip()
if line != "EndOfInput":
if line in section_map:
current_item = section_map[line]
elif current_item is not None:
current_item.append(line)
else:
print("Ignoring line: {}".format(line))
else:
break
return course_list, student_list, grade_list
def convert_names(name_list):
ret = OrderedDict()
for element in name_list:
id, name = element.split(SEPARATOR)
ret[id] = name
return ret
def convert_grades(grade_list):
ret = dict()
for element in grade_list:
course_id, student_id, grade_id = element.rsplit(SEPARATOR, 2)
ret.setdefault(student_id, dict())[course_id] = grade_id
return ret
def main():
course_list, student_list, grade_list = read_input_from_file()
student_list.sort()
student_dict = convert_names(student_list)
exam_stat_dict = convert_grades(grade_list)
for student_id in student_dict:
if student_id in exam_stat_dict:
grade_dict = exam_stat_dict[student_id]
grades_sum = sum([GRADE_DICT.get(item, 0) for item in grade_dict.values()])
print(SEPARATOR.join([student_id, student_dict[student_id], str(round(float(grades_sum/len(grade_dict)),2))]))
else:
print(SEPARATOR.join([student_id, student_dict.get(student_id), "0"]))
if __name__ == "__main__":
main()

Categories

Resources