## (solution) (c) Suppose a class takes a multiple-choice test. We're going to

(c) Suppose a class takes a multiple-choice test. We're going to experiment with alternative scoring mechanisms. For this problem you'll want to say from random import * (and use the methods  randrange and  choice, which you can look up in the text or using  help(random).)

Let's say you have these three global constants defined (a complete program might determine these values from reading a file; we're just doing it this way for convenience):

`NUMBER_OF_STUDENTS = 200 NUMBER_OF_QUESTIONS = 20 NUMBER_OF_CHOICES = 4 # 3 choices is A/B/C, 4 choices is A/B/C/D, 5 is A/B/C/D/E`

(c.1) Write a function called generate_answers that generates and returns a string of letters representing the correct answers to the test. (Of course answers to real tests aren't chosen randomly! We're just doing it this way to produce some test data to use when we score students' answers.) The length of the string should be the number of questions; each character in the string should be chosen randomly from the first n letters of the alphabet (where n is the number of choices). [Use the choice() method.]

Call generate_answers to generate the answers we'll use; assign the result to another global constant called ANSWERS.

(c.2) Ideally, we'd read the students and their exam answers from a file. But to save you some time, we'll skip the file-reading part and just generate a random list of students and their answers. To start with, let's say that each student is represented by a string for the student's name or ID and a string representing the student's answers to each question. [Are you thinking of a namedtuple with two fields? You should be.]

`Student = namedtuple('Student', 'name answers') s1 = Student('Jones, Jane', 'ABCCDAABAABCBBCACCAD') s2 = Student('Smith, Sam', 'BADACCABADCCABDDCBAB')`

Write the function  random_students that uses the global constants above to generate and return a list of Student namedtuples. The size of the list is the number of students. The names can be randomly generated as you did in an earlier lab or, to save time, you can just generate a random ID number using randrange(). The string representing the student's answers should be generated precisely the same way as you generated the correct answers (so don't duplicate any code!).

(c.3) Modify the Student namedtuple to add two fields, one containing a list of scores on each question (1 if the student's answer matches the correct answer and 0 otherwise) and the other a number representing the sum of the list of question scores:

`Student = namedtuple('Student', 'name answers scores total') s1 = Student('Jones, Jane', 'ABCCDAABAABCBBCACCAD', [1, 0, 1, 1, 1, 0, ...], 10) s2 = Student('Smith, Sam', 'BADACCABADCCABDDCBAB', [0, 1, 0, 0, 0, 1, ...], 5)`

Then modify your  random_students function to generate these student records with scores.

Generate your list of random students and then sort it by total, highest total to lowest, and print the top 10 students' names. (You can print them all; we're just trying to save paper and screen space here.) Also print the mean (average) score for all the students.

(c.4) This previous part used a conventional way to score multiple-choice exams. But you should expect the scores on this exam to be lower than on a typical exam: On a typical exam, students on the average are likelier to choose the correct answers than the wrong ones, but we generated our data completely at random. So let's think about how to generate more realistically distributed random data.

We can do this by defining a function called  generate_weighted_student_answer that takes a string (one character, the correct answer) and returns a string (one character, the student answer chosen randomly from the enhanced group of alternatives as described above). Write a new function called random_students2 that's based on your  random_students function but that generates each student's answer to each question by calling generate_weighted_student_answer.

Generate a new list of students using random_students2 and then sort it, highest total to lowest, and print the top 10 students' names. Also print the mean (average) score; it should be higher than in part (c.3).

(c.5) An unconventional way to score this exam would be to assign different weights to different questions. The instructor might assign those weights in advance, based on his or her judgement of which questions are harder or more important. But an even more unconventional way to assign the weights would be to derive them from the students' answers: The questions that were harder (i.e., that fewer people answered correctly) are worth more points than the easier ones.

One way to implement this would be to assign a number of points to each problem, that number being equal to the number of students who missed the problem. Write a function called question_weights that takes a list of Student records and returns a list of numbers, one number for each question on the test, where each number is the number of students who answered that question incorrectly. [Hint: It's helpful to attack complex data structures layer by layer. Try writing code that counts the number of wrong answers to a single question; then apply that in turn to all the questions.] Create another global constant that consists of the results of calling question_weights on your list of students from part (c.4).

Then write a function called Student_weighted_score that takes a Student record and the list of question weights and returns that Student record with its total field changed to reflect that student's score based on his or her correct answers and the corresponding question weights. Then apply  Student_weighted_score to each student on your list of students from part (c.4). Finally, sort the result, highest total to lowest, and print the top 10 students' names along with the mean (average) score for all the students.

Solution details:

STATUS

QUALITY

Approved

Sep 13, 2020

EXPERT

Tutor