Sorting hat ¶
I will use this script to be fair for assigning groups and scheduling. It uses a YAML file that puts students into their preferred slots if available.
In [13]:
Copied!
import random
import math
import yaml
import random
import math
import yaml
In [14]:
Copied!
yaml_file = r"""people:
- name: Mr. Bubbles
preferences: null
- name: Captain Crunch
preferences: null
- name: Sir Fluffington
preferences: null
- name: Baroness Sock Puppet
preferences: null
- name: Count Snickerdoodle
preferences: null
- name: Lord Wafflecone
preferences: null
- name: Emperor Noodlehead
preferences: null
- name: Sir Ticklemuffin
preferences: null
- name: Captain Squeezykins
preferences: null
- name: King Noodlewhisker
preferences: null
- name: Princess Butterfluff
preferences: null
- name: Dr. Popcornopolis
preferences: null
- name: Countess Gigglesnort
preferences: null
- name: Sir Pancakestack
preferences: null
- name: Professor Marshmallow
preferences: null
- name: Lady Whiskerfluff
preferences: null
- name: Captain Sprinkles
preferences: null
- name: Duchess Cuddlebug
preferences: null
- name: Emperor Puddingcup
preferences: null
- name: Count Tickletummy
preferences: null
- name: Baroness Bubblesocks
preferences: null
- name: Lady Jellybean
preferences: null
- name: King Muffinwhisker
preferences: null
events:
- label: Group 1
- label: Group 2
- label: Group 3
- label: Group 4
- label: Group 5
- label: Group 6
- label: Group 7
- label: Group 8
"""
yaml_file = r"""people:
- name: Mr. Bubbles
preferences: null
- name: Captain Crunch
preferences: null
- name: Sir Fluffington
preferences: null
- name: Baroness Sock Puppet
preferences: null
- name: Count Snickerdoodle
preferences: null
- name: Lord Wafflecone
preferences: null
- name: Emperor Noodlehead
preferences: null
- name: Sir Ticklemuffin
preferences: null
- name: Captain Squeezykins
preferences: null
- name: King Noodlewhisker
preferences: null
- name: Princess Butterfluff
preferences: null
- name: Dr. Popcornopolis
preferences: null
- name: Countess Gigglesnort
preferences: null
- name: Sir Pancakestack
preferences: null
- name: Professor Marshmallow
preferences: null
- name: Lady Whiskerfluff
preferences: null
- name: Captain Sprinkles
preferences: null
- name: Duchess Cuddlebug
preferences: null
- name: Emperor Puddingcup
preferences: null
- name: Count Tickletummy
preferences: null
- name: Baroness Bubblesocks
preferences: null
- name: Lady Jellybean
preferences: null
- name: King Muffinwhisker
preferences: null
events:
- label: Group 1
- label: Group 2
- label: Group 3
- label: Group 4
- label: Group 5
- label: Group 6
- label: Group 7
- label: Group 8
"""
In [15]:
Copied!
class Person:
def __init__(self, name: str, preferences: tuple = None):
self.name = name
if isinstance(preferences, str):
preferences = (preferences,)
self.preferences = preferences
def __str__(self):
prefs = self.preferences
if prefs is None:
prefs = "None"
return f"{self.name} preferences: {', '.join(prefs)}"
class Event:
def __init__(self, label: str, n_slots: int = 1):
self.label = label
self.n_slots = n_slots
self.slots = []
def add_person(self, person):
if self.slots_remaining > 0:
self.slots.append(person)
@property
def slots_remaining(self):
return self.n_slots - len(self.slots)
class Schedule:
def __init__(self, events):
self.events = events
def __str__(self):
sched = ""
for event in self.events:
sched += (
event.label
+ ": "
+ ", ".join([person.name for person in event.slots])
+ "\n"
)
return sched
def assigner(events: list, people: list, n_shuffle: int = 100):
unassigned_people = list(people)
event_labels = [event.label for event in events]
for _ in range(n_shuffle):
random.shuffle(unassigned_people)
# Put all students in their first choice, or the next available choice
for person in unassigned_people:
if person.preferences is not None:
for preference in person.preferences:
event_idx = event_labels.index(preference)
if events[event_idx].slots_remaining > 0:
events[event_idx].add_person(person)
break
else:
event_idxs = list(range(len(events)))
for _ in range(n_shuffle):
random.shuffle(event_idxs)
for event_idx in event_idxs:
if events[event_idx].slots_remaining > 0:
events[event_idx].add_person(person)
break
return Schedule(events)
def optimize_slots(n_events, n_people):
base_n_slots = math.floor(n_people / n_events)
slots = [base_n_slots] * n_events
n_error = n_people - n_events * base_n_slots
if n_error > 0:
for _ in range(n_error):
slots[_] += 1
return slots
def main():
info = yaml.safe_load(yaml_file)
people = [Person(**person_info) for person_info in info["people"]]
events = [Event(**event_info) for event_info in info["events"]]
event_slots = optimize_slots(len(events), len(people))
for i, event_slot in zip(range(len(events)), event_slots):
events[i].n_slots = event_slot
schedule = assigner(events, people, n_shuffle=100)
print(schedule)
class Person:
def __init__(self, name: str, preferences: tuple = None):
self.name = name
if isinstance(preferences, str):
preferences = (preferences,)
self.preferences = preferences
def __str__(self):
prefs = self.preferences
if prefs is None:
prefs = "None"
return f"{self.name} preferences: {', '.join(prefs)}"
class Event:
def __init__(self, label: str, n_slots: int = 1):
self.label = label
self.n_slots = n_slots
self.slots = []
def add_person(self, person):
if self.slots_remaining > 0:
self.slots.append(person)
@property
def slots_remaining(self):
return self.n_slots - len(self.slots)
class Schedule:
def __init__(self, events):
self.events = events
def __str__(self):
sched = ""
for event in self.events:
sched += (
event.label
+ ": "
+ ", ".join([person.name for person in event.slots])
+ "\n"
)
return sched
def assigner(events: list, people: list, n_shuffle: int = 100):
unassigned_people = list(people)
event_labels = [event.label for event in events]
for _ in range(n_shuffle):
random.shuffle(unassigned_people)
# Put all students in their first choice, or the next available choice
for person in unassigned_people:
if person.preferences is not None:
for preference in person.preferences:
event_idx = event_labels.index(preference)
if events[event_idx].slots_remaining > 0:
events[event_idx].add_person(person)
break
else:
event_idxs = list(range(len(events)))
for _ in range(n_shuffle):
random.shuffle(event_idxs)
for event_idx in event_idxs:
if events[event_idx].slots_remaining > 0:
events[event_idx].add_person(person)
break
return Schedule(events)
def optimize_slots(n_events, n_people):
base_n_slots = math.floor(n_people / n_events)
slots = [base_n_slots] * n_events
n_error = n_people - n_events * base_n_slots
if n_error > 0:
for _ in range(n_error):
slots[_] += 1
return slots
def main():
info = yaml.safe_load(yaml_file)
people = [Person(**person_info) for person_info in info["people"]]
events = [Event(**event_info) for event_info in info["events"]]
event_slots = optimize_slots(len(events), len(people))
for i, event_slot in zip(range(len(events)), event_slots):
events[i].n_slots = event_slot
schedule = assigner(events, people, n_shuffle=100)
print(schedule)
In [16]:
Copied!
main()
main()
Group 1: Captain Crunch, Sir Ticklemuffin, Countess Gigglesnort Group 2: Captain Sprinkles, Count Snickerdoodle, Duchess Cuddlebug Group 3: Lord Wafflecone, Count Tickletummy, King Noodlewhisker Group 4: Sir Pancakestack, Mr. Bubbles, Baroness Bubblesocks Group 5: Professor Marshmallow, Sir Fluffington, Baroness Sock Puppet Group 6: Emperor Puddingcup, Captain Squeezykins, Emperor Noodlehead Group 7: Lady Whiskerfluff, King Muffinwhisker, Lady Jellybean Group 8: Dr. Popcornopolis, Princess Butterfluff