|
from typing import List |
|
import csv |
|
import math |
|
import random |
|
|
|
# Step 1: Create a file "students.csv" in the same directory as this script, |
|
# containing each student's information in the following format: |
|
# >> Student 1 Name,student1eid,0 |
|
# >> Student 2 Name,student2eid,0 |
|
# If you do not create this file, you'll receive a FileNotFoundError |
|
# when you run the script. You should only need to create this file |
|
# once; it will be updated for subsequent runs of the script. |
|
|
|
# Step 2: Customise to the maximum group size that you would prefer. Groups of |
|
# at most 3 or 4 students are recommended. |
|
GROUP_SIZE = 4 |
|
|
|
class Student: |
|
""" |
|
A simple class representing a student's name, EID, and the number of times |
|
they have served as a group's scribe. |
|
""" |
|
def __init__(self, name: str, eid: str, scribeships: int): |
|
self.name = name |
|
self.eid = eid |
|
self.scribeships = scribeships |
|
|
|
def __repr__(self): |
|
return f'<Student name=\"{self.name}\" scribeships={self.scribeships}>' |
|
|
|
def __lt__(self, other): |
|
return self.scribeships < other.scribeships |
|
|
|
def add_scribeship(self): |
|
"""Add a scribeship to this student's record.""" |
|
self.scribeships += 1 |
|
|
|
def fair_partition(lst: List[Student], |
|
part_size: int, |
|
distribute: List[Student] = []) -> List[List[Student]]: |
|
""" |
|
Partition the specified list "fairly" into groups with the specified size, |
|
ensuring that all groups have a roughly even number of constituents. |
|
Furthermore, ensures that one of the people in the distribute list is |
|
allocated to each group as far as is possible. Returns a list of the |
|
generated partitions. |
|
""" |
|
# Precondition: ensure that set difference works properly |
|
if len(distribute) > len(lst): |
|
raise ValueError( |
|
"Cannot have more mandatory constituents than constituents.") |
|
|
|
# Shuffle the list to ensure random allocation |
|
shuffled = lst.copy() |
|
random.shuffle(shuffled) |
|
# Determine how many partitions into which we can divide the constituents |
|
num_groups = math.ceil(len(lst) / part_size) |
|
partitions = [] |
|
|
|
# Handle mandatory distribution first |
|
for i in range(num_groups): |
|
if i < len(distribute): |
|
new_member = distribute[i] |
|
else: |
|
new_member = shuffled[i - len(distribute)] |
|
# Record that this constituent has been distributed |
|
partitions.append([new_member]) |
|
shuffled.remove(new_member) |
|
new_member.add_scribeship() |
|
|
|
# Distribute remaining constituents |
|
for i in range(len(shuffled)): |
|
partitions[i % num_groups].append(shuffled[i]) |
|
|
|
return partitions |
|
|
|
def write_zoom_csv(groups: List[List[Student]]) -> None: |
|
""" |
|
Create a file named "zoom.csv" containing a description of the specified |
|
groups that is compatible with Zoom's breakout room format. |
|
""" |
|
with open('zoom.csv', 'w', newline='') as f: |
|
writer = csv.writer(f) |
|
writer.writerow(['Pre-assign Room Name', 'Email Address']) |
|
for i in range(len(groups)): |
|
scribe_name = groups[i][0].name |
|
writer.writerows( |
|
[[f'Group {i + 1} (Scribe: {scribe_name})', |
|
f'{s.eid}@eid.utexas.edu'] for s in groups[i]]) |
|
|
|
def write_students_csv(students: List[Student]) -> None: |
|
""" |
|
Re-write the file "students.csv" with updated scribeship counts. |
|
""" |
|
with open('students.csv', 'w', newline='') as f: |
|
csv.writer(f).writerows( |
|
[[s.name, s.eid, str(s.scribeships)] for s in students]) |
|
|
|
if __name__ == '__main__': |
|
# Read in all students |
|
students = [] |
|
with open('students.csv', 'r') as f: |
|
reader = csv.reader(f) |
|
for name, eid, scribeships in reader: |
|
students.append(Student(name, eid, int(scribeships))) |
|
|
|
# Determine the first student who can serve as a scribe |
|
students.sort(reverse=True) |
|
max_scribeships = students[0].scribeships |
|
distribute_start = 0 |
|
while (distribute_start < len(students) and |
|
students[distribute_start].scribeships == max_scribeships): |
|
distribute_start += 1 |
|
if distribute_start == len(students): |
|
distribute_start = 0 |
|
|
|
# Create groups each having a scribe |
|
candidate_scribes = students[distribute_start:] |
|
candidate_scribes.reverse() |
|
groups = fair_partition(students, GROUP_SIZE, candidate_scribes) |
|
|
|
# Write out files |
|
write_zoom_csv(groups) |
|
write_students_csv(students) |