0

I am in a beginning coding class and I can not seem to turn the basics I'm taught into a working program with a list this complicated. What functions should I be using to do this?

At this point we have not discussed importing any extra features (numpy etc) and I know people use lambda a lot (though I don't really understand what it does), but that has not been introduced in this class.

#This is an example of the structure of a student dictionary
#They have an id number
#They have a first name, last name and a list of assignments
#Assignments are tuples of an assignment name and grade
#The grade is a 4 point scale from 0 to 4
'''
student_list = [{'id': 12341, 'first_name': 'Alice', 'last_name': 'Anderson',
     'assignments': [('assignment_1', 0), ('assignment_2', 2), ('assignment_3', 4)]},

 {'id': 12342, 'first_name': 'Boris', 'last_name': 'Bank',
   'assignments': [('assignment_1', 1), ('assignment_2', 3), ('assignment_3', 0)]},

 {'id': 12343, 'first_name': 'Carl', 'last_name': 'Cape',
   'assignments': [('assignment_1', 2), ('assignment_2', 4), ('assignment_3', 1)]},

 {'id': 12344, 'first_name': 'Didi', 'last_name': 'Dawson',
   'assignments': [('assignment_1', 3), ('assignment_2', 0), ('assignment_3', 2)]},

 {'id': 12345, 'first_name': 'Ed', 'last_name': 'Enders',
   'assignments': [('assignment_1', 4), ('assignment_2', 1), ('assignment_3', 3)]}]

#This function should return a list of the n student dictionaries with the
#highest grades on the assignment passed in as assignment name
#If there is a tie then it is broken by returning the student(s) with the
#lowest id number(s)
def highest_n_grades(students, assignment_name, n):

Edit

Sorry, I'm not trying to get an answer. I see how that looks. I feel like I've written out and deleted a million things and that's my problem. I'm having trouble even getting started.

I was hoping for a point in the right direction in terms of maybe what commands can grab highest grades etc. all I really have so far is something like:

def highest_n_grades(student_list):
  for s in student_list:
    for assignment_name, grade in s['assignments']:
        if int(grade) >= 4:
            print(assignment_name, grade)

highest_n_grades(student_list)

But I know that's not even really getting me started. It doesn't have three inputs and it's not looking for the max, it's looking for the manually entered value 4, and it's not even coming close to tying at back to student names or making another list.

Edit 2

Also tried that gave an error I was trying to sort the dictionary rather than the list.

def highest_n_grades(student_list, assignment_name):
  for s in student_list:
    for assignment_name in s['assignments'][1]:
      s['assignments'][1] = assignment_name
      s.sort(key=assignment_name)
    print(student_list)

highest_n_grades(student_list, assignment_name='assignment_1' )

Edit 3

OK, I've maybe made a little headway?

newlist2 = sorted(newlist, key=lambda k: k['assignments'][0], reverse = True)
newlist3 = sorted(newlist, key=lambda k: k['assignments'][1], reverse = True)
newlist4 = sorted(newlist, key=lambda k: k['assignments'][2], reverse = True)

These seem to be sorting by assignment. I don't understand what lambda is doing, but I at least can generate a list with the highest grade coming up first. I think that's a baby step.

Edit 4

Here is a function I created. It seems to get me what I want, it outputs the highest 3 students, but it prints it 5 times? and I know this isn't really flexible but it's a start.

def highest_n_grades(student_list,  n):
  for s in student_list:
    newlist = sorted(student_list, key=lambda k: k['assignments'][0], reverse=True)
    print(newlist[:n])

highest_n_grades(student_list, 3)

output:

[{'id': 12345, 'first_name': 'Ed', 'last_name': 'Enders', 'assignments': [('assignment_1', 4), ('assignment_2', 1), ('assignment_3', 3)]}, {'id': 12344, 'first_name': 'Didi', 'last_name': 'Dawson', 'assignments': [('assignment_1', 3), ('assignment_2', 0), ('assignment_3', 2)]}, {'id': 12343, 'first_name': 'Carl', 'last_name': 'Cape', 'assignments': [('assignment_1', 2), ('assignment_2', 4), ('assignment_3', 1)]}]
[{'id': 12345, 'first_name': 'Ed', 'last_name': 'Enders', 'assignments': [('assignment_1', 4), ('assignment_2', 1), ('assignment_3', 3)]}, {'id': 12344, 'first_name': 'Didi', 'last_name': 'Dawson', 'assignments': [('assignment_1', 3), ('assignment_2', 0), ('assignment_3', 2)]}, {'id': 12343, 'first_name': 'Carl', 'last_name': 'Cape', 'assignments': [('assignment_1', 2), ('assignment_2', 4), ('assignment_3', 1)]}]
[{'id': 12345, 'first_name': 'Ed', 'last_name': 'Enders', 'assignments': [('assignment_1', 4), ('assignment_2', 1), ('assignment_3', 3)]}, {'id': 12344, 'first_name': 'Didi', 'last_name': 'Dawson', 'assignments': [('assignment_1', 3), ('assignment_2', 0), ('assignment_3', 2)]}, {'id': 12343, 'first_name': 'Carl', 'last_name': 'Cape', 'assignments': [('assignment_1', 2), ('assignment_2', 4), ('assignment_3', 1)]}]
[{'id': 12345, 'first_name': 'Ed', 'last_name': 'Enders', 'assignments': [('assignment_1', 4), ('assignment_2', 1), ('assignment_3', 3)]}, {'id': 12344, 'first_name': 'Didi', 'last_name': 'Dawson', 'assignments': [('assignment_1', 3), ('assignment_2', 0), ('assignment_3', 2)]}, {'id': 12343, 'first_name': 'Carl', 'last_name': 'Cape', 'assignments': [('assignment_1', 2), ('assignment_2', 4), ('assignment_3', 1)]}]
[{'id': 12345, 'first_name': 'Ed', 'last_name': 'Enders', 'assignments': [('assignment_1', 4), ('assignment_2', 1), ('assignment_3', 3)]}, {'id': 12344, 'first_name': 'Didi', 'last_name': 'Dawson', 'assignments': [('assignment_1', 3), ('assignment_2', 0), ('assignment_3', 2)]}, {'id': 12343, 'first_name': 'Carl', 'last_name': 'Cape', 'assignments': [('assignment_1', 2), ('assignment_2', 4), ('assignment_3', 1)]}]
halfer
  • 19,824
  • 17
  • 99
  • 186
tsb
  • 53
  • 4
  • 3
    Hi, just posting your homework task probably will not get you any help. At least show some effort and make your program so that it runs, even if it does not do anything. Then you could start looping through the data and output it, just to get started. Any tutorial on loops will do, e.g. https://www.python-course.eu/python3_for_loop.php – handle Oct 07 '18 at 17:38
  • 1
    What have you tried? Can you provide a [MCVE]? This appears to be a homework assignment, and this is not really a "here is my homework requirements, please provide a fully working example" type of site. Please post what you have tried, so we can help **POINT** you in the right direction. – artomason Oct 07 '18 at 17:39
  • Sorry. I'm not trying to get answer. I see how that looks. I feel like I've written out and deleted a million things and that's my problem. I'm having trouble even getting started.... I was hoping for a point in the right direction in terms of maybe what commands can grab highest grades etc. all I really have so far is something like def highest_n_grades(student_list): for s in student_list: for assignment_name, grade in s['assignments']: if int(grade) >= 4: print(assignment_name, grade) highest_n_grades(student_list) – tsb Oct 07 '18 at 17:58
  • Please post what you have, and we can help you from there. Without being able to gauge exactly where you are in the process, it is impossible for us to help you. Just edit your answer and provide code, please do not do that in the comments – artomason Oct 07 '18 at 17:59
  • sorry, added an edit. I didn't see the edit button at first. – tsb Oct 07 '18 at 18:02
  • How does your data structure look? Can you post your dictionary? That would probably be the best place to start; organizing your dictionary and understanding the structure. – artomason Oct 07 '18 at 18:05
  • I have a list of dictionaries, with one of the values of each dictionary being a list of tuples. I think it's too many layers for me to make sense of as a beginner. @artomason I added the list of dictionaries I made as a test to work with things. Is that what you wanted? – tsb Oct 07 '18 at 18:11
  • 1
    Ok, a very helpful strategy would be for you to just start printing the values. for example print out `student_list[0]` then see the output of `student_list[0]['id']` then see the output of `student_list[0]['assignments'][0]` this will help you understand how to fetch data you need. and also in your loops you'd know which values you're working on – Nina Oct 07 '18 at 18:13
  • Have you studied list sorting in your class yet? If yes, that's a hint. And here's some documentation on list sorting (https://www.w3schools.com/python/ref_list_sort.asp) – slider Oct 07 '18 at 18:13
  • I think sorting has been mentioned some (I've been reading resources all over the place so everything is jumbled now), but I'm hung up on the fact that it's dictionaries, and I thought you couldn't sort dictionaries? – tsb Oct 07 '18 at 18:19
  • 1
    @tsb I was suggesting sorting `student_list` which is a `list` of dictionaries. Specifically, sorting with descending order based on the assignment name that is passed in (so we would be specifying a `key` attribute in the sort function). Can you see the idea behind this approach now? – slider Oct 07 '18 at 18:38
  • I would sort the `value` received for assignments using [`sorted()`](https://docs.python.org/3/howto/sorting.html#key-functions) in descending order which will ensure that the first entry in the assignment `value` is the highest grade. – artomason Oct 07 '18 at 18:54
  • As @slider suggested you could also sort the `student_list` by `student_id` before iterating through it. – artomason Oct 07 '18 at 18:59
  • @slider, added something I tried in an edit, but it still seems like it's trying to sort a dictionary. I don't really understand how to add a key that is inside the dictionary inside the list – tsb Oct 07 '18 at 19:05
  • are you allowed to use lambda, you said you havent touched on it yet or is that not acceptable – vash_the_stampede Oct 07 '18 at 19:24
  • well I just managed to sort my list using lambda (edit above) but to be honest I don't know what it's doing, just that it worked – tsb Oct 07 '18 at 19:26
  • @tsb Very cool. Note that `key` has to be a function. And lambda is just shorthand for defining a function. So what you can do is define a function called `def get_assignment_one_grade(student): return student['assignments'][0][1]` and then your sorting code becomes `newlist2 = sorted(newlist, key=get_assignment_one_grade, reverse = True)` – slider Oct 07 '18 at 19:39
  • ok. first off, thank you all for your help. I'm adding another edit with a function. It doesn't get me everything I need but I think it's getting me closer. Can you explain why it prints five times when I run it? – tsb Oct 07 '18 at 19:43
  • You don't need to sort it inside a `for` loop. That's why it's printing it 5 times. – slider Oct 07 '18 at 19:53

2 Answers2

1

This can be done using lambda and sorted. When using sorted with lambda we set first key=lambda x:. Now you can think of that x representing a list index, so to sort by assignment_1 we are going to want to go x['assignments'] this will take us to the assignments, then the next step, if our assignment is assignment_1 we know that that is the 0 index of assignments so together it would be key=lambda x: x['assignments'][0]. Now we can also sort a secondary option and that will be our tie breaker, we will use x[id] and will be in a tuple with our primary sorting factor. Of course we should use reverse = True to get descending scores, but since we want our tiebreaker to be in ascending order we can offset the reverse on id using -(x['id'])

Altogether the sort looks like this :

lista = sorted(students, key=lambda x: (x['assignments'][0], -(x['id'])), reverse = True)

The tricky part would be choosing the proper assignment index for the passed assignment, for that you could use .split('_')[1] (when using .split('_') on 'assignment_1' we generate a new list that is ['assignemnt', '1'] in this case we can take now the [1] index of .split() which is 1 as an int and subtract 1 to get 0 which is the corresponding index, as well as for the rest being that all are off 1 from their index.

def highest_n_grades(students, assignment_name, n):
    y = int(assignment_name.split('_')[1]) - 1
    lista = sorted(students, key=lambda x: (x['assignments'][y], 'id'), reverse = True)
    return lista [:n]   

print(highest_n_grades(student_list, 'assignment_1', 3))
# [{'id': 12345, 'first_name': 'Ed', 'last_name': 'Enders', 'assignments': [('assignment_1', 4), ('assignment_2', 1), ('assignment_3', 3)]}, {'id': 12344, 'first_name': 'Didi', 'last_name': 'Dawson', 'assignments': [('assignment_1', 3), ('assignment_2', 0), ('assignment_3', 2)]}, {'id': 12343, 'first_name': 'Carl', 'last_name': 'Cape', 'assignments': [('assignment_1', 2), ('assignment_2', 4), ('assignment_3', 1)]}]

Demonstration of tie-breaker case, using pseudo scores:

print(highest_n_grades(student_list, 'assignment_1', 3))
# [{'id': 12344, 'first_name': 'Didi', 'last_name': 'Dawson', 'assignments': [('assignment_1', 4), ('assignment_2', 0), ('assignment_3', 2)]}, {'id': 12345, 'first_name': 'Ed', 'last_name': 'Enders', 'assignments': [('assignment_1', 4), ('assignment_2', 1), ('assignment_3', 3)]}, {'id': 12342, 'first_name': 'Boris', 'last_name': 'Bank', 'assignments': [('assignment_1', 2), ('assignment_2', 3), ('assignment_3', 0)]}]

Further reading

on .split()

https://docs.python.org/3/library/stdtypes.html

on using sorted

https://docs.python.org/3/library/functions.html https://wiki.python.org/moin/HowTo/Sorting

vash_the_stampede
  • 4,590
  • 1
  • 8
  • 20
  • The goal was to not completely solve it for the OP but to guide them to the solution since this is a homework problem. – slider Oct 07 '18 at 19:59
  • @slider yeah but also I don't think SO was made for step by step online guidance in a comment box, it was getting ridiculous the amount of comments that were leading here but unconstructed, what good is a question with a 30 comment solution – vash_the_stampede Oct 07 '18 at 20:00
  • yeah, shoot. I do appreciate this, and I see that it works, but I feel like I've lost some learning opportunity. What does the [1] in the y = int(assignment_name.split('_')[1]) - 1 do? is it saying to look for the second argument in the highest_n_grades function? – tsb Oct 07 '18 at 20:18
  • also on that vain, do you know of a resource that would be more step by step guidance? – tsb Oct 07 '18 at 20:21
  • @tsb I tried to be as informative as I could along the way, I didn't want to give you the answer but the only way I could explain what is going on and what you need to learn is to walk through it step by step, I added to the paragraph about `.split()` and will include some links – vash_the_stampede Oct 07 '18 at 20:30
  • Thank you @vash_the_stampede, that explanation for .split() helped a lot. This is my first time learning a coding language (which I imagine is very obvious) so even figuring out the best way to learn it presents its challenges. – tsb Oct 07 '18 at 20:37
  • @tsb ha yeah its a little wonky in the beginning but no excuses this my first time as well, halfway through month two alone at home, as long as you are hungry to learn I promise everything is on here somewhere or available, keep learning :) – vash_the_stampede Oct 07 '18 at 20:43
  • I also think y = int(assignment_name[-1]) - 1 works instead of using the split function. – tsb Oct 10 '18 at 03:16
1

This is a difficult assignment for a beginner's course. The difficulties are lambdas, multiple-key sorting, lists, list slices and tuples, dictionaries and even ordered versus unordered data types. I've been programming in Python for 10 years and didn't find it straightforward.

A lambda is a tiny function that you define on the fly. sorted() takes a function as its second argument. It needs to call this function for each student to generate a sort key. The sort function compares the sort keys of two students to decide which student goes first in the sort.

A good place to start with lambdas is to remember that:

id_key = lambda x: x[0]

is equivalent to:

def id_key(x):
    return x[0]

Furthermore

sorted(students, key=lambda x: x[0])

is equivalent to:

sorted(student, key=id_key)

For sorting on multiple values I'd be looking at stable sorts and their properties. Stable sort algorithms are great for sorting on more than one value. Most Python sorting functions are 'stable'.

Here's a solution using the present structure:

def sort_by_grade_then_id(grades):
    # sort (id, grade) tuples high grades, low ids first
    sorted_by_id = sorted(grades, key=lambda student: student[0])
    sorted_by_id_and_assignment_grade = sorted(sorted_by_id,
        key=lambda student: student[1], reverse=True)
    return sorted_by_id_and_assignment_grade


def highest_n_grades(students, assignment_name, n):
grades = []
for student in students:
    for assignment, grade in student['assignments']:
        if assignment_name == assignment:
            grades.append((student['id'], grade))
return sort_by_grade_then_id(grades)[:n]    

>>> print(highest_n_grades(student_list, 'assignment_2', 2))
[(12343, 4), (12342, 3)]

But if you now want the student's name rather than his/her id, you'll have to do another serial search to get it.

As a different approach, the following copies the original list-based student database into a dictionary-based one.

from copy import copy

students_dict = {student['id']: student for student in copy(student_list)}
for student in students_dict.values():
    student['assignments'] = dict(student['assignments'])

Listing the top grades becomes:

def highest_n_grades_dict(students, assignment_name, n):
    grades = [
        (id, student['assignments'][assignment_name])
        for id, student
        in students.items()
    ]
    return sort_by_grade_then_id(grades)[:n]

It doesn't matter with just a few students, but if you had many students and many assignments, this new version would be faster. You can also use the student database to look stuff up now, rather than having to search and match.

As an example:

print('Highest grades dict version...')
grades = highest_n_grades_dict(students_dict, 'assignment_2', 2)
print(grades)
print("...and dict structure easily allows us to get other student details")
names_and_grades = [
    (students_dict[id]['first_name'] + ' ' + students_dict[id]['last_name'], grade)
    for id, grade
    in grades]
print(names_and_grades)
>>> python grades.py
Highest grades dict version...
[(12343, 4), (12342, 3)]
...and dict structure easily allows us to get other student details
[('Carl Cape', 4), ('Boris Bank', 3)]

Side note: if you deal with tuples a lot, you might be interested in named tuples, as they often make tuple-related code (including lambda functions) easier to read, write and understand. Have a look at my recent answer to this question for an example.

Nic
  • 1,518
  • 12
  • 26
  • Thank you! Your statements are really reaffirming. I thought this was an unreasonably complex data set for how early into this program I am... last week I wrote a program that counted numbers and added fizz to it if it was divisible by 3.... This is one of 6 functions I have to write with this data set. While the other answer works and was helpful, I think something more like what you've written might be what they are going for. Hopefully I can take this knowledge to work on my other functions – tsb Oct 08 '18 at 00:47
  • No worries! Thinking about this problem further, my preferred method is actually to rewrite the data as dictionaries before doing any processing. My guess is it would actually be easier and faster to do that than leave the data as it is. – Nic Oct 08 '18 at 01:03
  • added the code to rewrite student database as a dictionary. – Nic Oct 08 '18 at 07:31