Assignment 1 of CSC148 (Intro to Computer Science) University of Toronto 2023 Assignment 1: Forming Optimal Groups
Introduction In the field of teaching university courses, a question that has received considerable attention is this:
What is the best way to put students into groups?
There are arguments for making groups heteregenous, and other arugments for making them homogeneous. What is best may depend on the kind of work, the size of the group, or what attributes we are basing the grouping on. For example, we might want groups with heterogeneous programs of study, so students bring different perspectives. Or we might want homogeneous neighbourhoods, so students from who live nearby can meet to work together in person. Or we may want a combination of these criteria. Of course, to apply criteria like these, we need the relevant information about the students (their program of study, or college, for instance). We can get that by surveying the students.
Luckily, with your extensive programming knowledge that you have acquired from taking CSC148, you will be able to write a program that lets an instructor form student groups in the way that they think is best.
Your task is to complete a program that analyzes an instructor’s criteria for good groups, plus data extracted from a student survey, to make groups that are optimal with respect to the criteria. We have broken down the assignment into several tasks that are outlined in detail in this handout.
Learning Goals By the end of this assignment you should be able to:
read complex code you didn’t write and understand its design and implementation, including:
given a partial implementation of a class, complete it, including:
General Guidelines -You may complete this assignment individually or with a partner. -Please read this handout carefully and ask questions if there are any tasks you do not understand. -The tasks are not designed to be equally difficult, or even in increasing order of difficulty. They are just laid out in logical order. -Although implementing a complex application can be challenging at first, we will guide you through a progression of tasks, in order to gradually build pieces of your implementation. Your responsibility includes reading through this handout and the starter code we provide, carefully, and understanding how the classes work together in the context of the application.
Coding Guidelines These guidelines are designed to help you write well-designed code that will adhere to the interfaces we have defined (and thus will be able to pass our test cases).
You must:
You must NOT:
You may need to:
While writing your code you can assume that all arguments passed to the methods you have been given in the starter code will respect the preconditions outlined in the methods’ docstrings.
We have included a docstring for a str method in many of the classes you will write. We have not specified what a string representation of these objects will look like; this is up to you to pick something that is useful to you in your debugging process. We will not be grading for any particular string representation.
Your Task
Complete the classes and methods given to you in the starter code to create a piece of software that keeps track of survey data and uses the results of that data to make optimal groups of students according to provided criteria.
You are encouraged to complete this assignment in the order outlined in the tasks below but you may choose a different order if you wish.
Make sure you complete each “Test your code!” section before moving on to the next task to make sure that your code works as expected.
In each of the tasks below you are encouraged to read questions in the “Something to think about” sections. You are not required to answer these questions for this assignment but thinking about them might help you write better code!
Task 1: Get the starter code and read the documentation Download the file a1.zip (to be provided shortly) that contains the starter code Unzip the file and place the contents in pycharm in your a1 folder (remember to set your a1 folder as a sources root) You should see the following files: course.py criterion.py grouper.py survey.py tests.py example_tests.py example_usage.py a folder called data containing several .json files (example_course.json, example_survey.json, generated_course_hetero.json, generated_course_lonely.json, generated_course.json, longer_survey_hetero.json, longer_survey_lonely.json, and longer_survey.json). For this assignment, you will be required to edit and submit the files that do not start with example_.
If you look at these files you will notice that you have been given the headers and docstrings for many classes and methods, which describe how you are expected to implement them. You will complete them in the rest of this assignment’s tasks.
Task 2: Complete the Student class in course.py The Student class represents a student who can be enrolled in a university course.
What to do:
Note: The Student.has_answer method asks you to check if a student has a valid answer to a given question. We don’t yet have a way to determine if an answer is valid, and we won’t until we complete Task 5. You may need to come back and finish this method after completing Task 5.
Task 3: Complete the Course class in course.py The Course class represents a university course.
What to do:
Task 4: Complete the question classes in survey.py The file survey.py contains an abstract Question class, and the following classes for representing different types of questions that you might find on a survey:
MultipleChoiceQuestion NumericQuestion YesNoQuestion CheckboxQuestion Each of these classes defines the text of a question, and specifies what are valid answers to that particular type of question.
We have not defined any inheritance hierarchy between these classes. You get to decide what it should be.
What to do: -Define an inheritance hierarchy between these classes, following these rules: The abstract class Question must be a superclass of all the others.
All other question classes must inherit from the abstract class Question either directly or indirectly.
At least one non-abstract question class should inherit from another non-abstract question class. This means that your inheritance hierarchy will have more than two levels.
There are many possible inheritance structures you could choose. Remember that one of the requirements for this assignment is to avoid writing duplicate code. Think about which sort of inheritance structure best lets you avoid duplicate code.
Implement the methods in each of the question classes, as described in their method docstrings. You may remove a method that we included in the starter code in a child class if you want to inherit the method directly from the parent class, rather than override/extend it. Note: You may find the Python set type to be useful.
In tests.py, write your own tests for each public method in the YesNoQuestion class. You should have at least one test for each public method (other than init and str). You need to write tests for inherited methods, even if they are not overridden in the YesNoQuestion class.
For example, even if you structure your code so that the YesNoQuestion class inherits its validate_answer method without modification from another class, you still need to write tests for the validate_answer method when called on an instance of YesNoQuestion.
Note: The validate_answer methods ask you to check if an answer is a valid answer for this question. You might not have enough information about the Answer class in order to complete this method now, and you may need to come back and finish this method after completing Task 5.
Task 5: Complete the Answer class in survey.py The Answer class represents an answer to one of the questions you wrote classes for in Task 4. (By “answer” we mean a response someone might give to the question, not the correct answer. Notice that we don’t record a correct answer anywhere! This is because our surveys are meant to gather information about students, not as tests.)
What to do:
Task 6: Complete the criterion classes in criterion.py A criterion is a way of judging the quality of a group based on the group members’ answers to a particular question. (The plural of criterion is criteria.) For example, one criterion could be to want groups with homogeneous (i.e. the same) answers to a question asking what year they are in. Another criterion could be to want groups to have heterogeneous (i.e. different) answers to another question.
The criterion classes are the following:
Criterion HomogeneousCriterion HeterogeneousCriterion LonelyMemberCriterion We have not defined any inheritance hierarchy between these classes. You get to decide what it should be.
What to do:
The abstract class Criterion must be a superclass of all the others.
All other criterion classes should inherit from the abstract class Criterion, either directly or indirectly.
At least one non-abstract criterion class should inherit from another non-abstract criterion class. This means that your inheritance hierarchy will have more than two levels.
There are many possible inheritance structures you could choose. Remember that one of the requirements for this assignment is to avoid writing duplicate code. Think about which sort of inheritance structure best lets you avoid duplicate code.
Remember: You may remove a method defined in a child class if you wish to simply inherit the parent’s method directly.
Write your own tests for the HomogeneousCriterion.score_answers method in tests.py. You need to write these tests even if you decide to inherit score_answers from its parent class without overriding it.
Optional but recommended: Write your own tests in tests.py for the score_answers method in the other criterion classes (except for the abstract class Criterion).
Run the tests in example_tests.py, and ensure the tests in the TestHomogeneousCriterion, TestHeterogeneousCriterion, and TestLonelyMemberCriterion classes now pass.
Task 7: Complete the Group class in grouper.py The Group class represents a collection of one or more students.
What to do:
Task 8: Complete the Grouping class in grouper.py The Grouping class represents a collection of Group instances. An instance of a Grouping class can be used to represent every student in a course, divided up into groups.
What to do:
Task 9: Complete the Survey class in survey.py The Survey class represents a collection of questions. It also associates each question with a criterion (indicating how to judge the quality of a group based on their answers to the question) and a weight (indicating the importance of this question in deciding how to group students).
What to do
Hint: Read the documentation for Survey._get_criterion and Survey._get_weight carefully before you implement the initializer. Write at least one of your own tests for each public method in the Survey class in tests.py, other than the init, str, len, and contains methods.
Task 10: Complete the grouper classes in grouper.py The grouper classes represent different techniques for deciding how to split all the students in a course into groups. You will be implementing three different groupers, each using a different algorithm. The file Groupers.pdf Download Groupers.pdfwalks through a detailed example for each of the groupers. Make sure you understand the algorithms before you start writing code.
Note that this task is intended to be the most algorithmically challenging part of the assignment. We encourage you to make sure you have completed the other tasks before investing significant time in this one.
The grouper classes are the classes in grouper.py that have “Grouper” in their name:
Grouper AlphaGrouper GreedyGrouper SimulatedAnnealingGrouper Unlike the criterion classes and question classes, the inheritance structure between the grouper classes has been given to you. You should NOT change the inheritance structure between the grouper classes.
What to do:
You may find the function sort_students from the course.py file helpful. There are also several helper functions defined in grouper.py for you to use. Read their docstrings carefully, but you are not expected to understand all of the code in these helper functions.
You may assume that there are more students in a course than the group size, however the group size may not evenly divide the number of students. That is, you can assume that there will be enough students to form at least two groups, and that you may end up with one group smaller than the “ideal” group size.
Write at least one test in tests.py for each of the AlphaGrouper.make_grouping and GreedyGrouper.make_grouping methods.
Optional but recommended: Write additional tests in tests.py for the AlphaGrouper.make_grouping and GreedyGrouper.make_grouping methods. Note that it will be more challenging to write tests for SimulatedAnnealing.make_grouping, since that method involves randomness.
Run the tests in example_tests.py and ensure that all tests in the TestAlphaGrouper, TestGreedyGrouper, and TestSimulatedAnnealingGrouper classes now pass.
Task 11: Test your code again! Now that you have finished writing all the code, go back and run all the tests again. We strongly recommend you do much more testing than we have required above to convince yourself that your code is working correctly. The example_tests.py file might catch some bugs in your code but it will certainly not catch them all.
Ideally you would write up thorough tests for every public method in the tasks above in pytest. We have only required a small number of the tests that you should be running on your code, in order to balance giving you some practice writing proper unit tests with letting you use other less formal testing methods if you choose. We hope it is clear by now that we think testing is important!
Using example_usage.py At this point, you can also run the example_usage.py file again. This file should now run without errors (however this does not mean your code is bug-free).
The example_usage.py file will create a course and a survey and will use that survey to group the students into groups using the three different grouper classes. It generates a visualization of the results as a HTML file that you can open with your web browser.
To generate different groups, you can experiment with setting the values of the variables example and group_size on the first two lines of the if name == 'main' block. You can also try changing the number of iterations and initial temperature used to initialize the SimulatedAnnealingGrouper.
The visualization HTML file contains a plot of the scores of the groups created by the different algorithms, as well as some simple statistics about how well they do. This visualization is just for your own exploration of the algorithms, you do not need to do anything with it.
This project was completed on Pycharm. This project was given a grading of 90.81% (Class Average: 84.2%)