Created
April 8, 2023 14:53
-
-
Save chappyasel/cac568706875e73ecd1f6f6d6fd7db7f to your computer and use it in GitHub Desktop.
Generates group assignments based on a variety of parameters for GAI Collective events
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import itertools | |
from random import shuffle | |
from random import choice | |
""" | |
Given a range of attendee counts, number of groups numGroups, and number of | |
rounds of assignments numRounds, return a list of numRounds-item tuples | |
representing the assignment of each attendee to numRounds groups such that each | |
attendee meets with as many other attendees as possible while keeping group | |
sizes as equal as possible. Attendees should not be assigned to the same group | |
twice. | |
Start with attendees.max and numGroups. Then, subtract attendees.interval | |
attendee assignments at a time while keeping group sizes relatively equal down | |
to attendees.min. | |
""" | |
################### CONFIGURATION ################### | |
# the minimum, maximum, and interval of attendees to calculate assignments for | |
attendees = { | |
"min": 100, | |
"max": 200, | |
"interval": 10, | |
} | |
# the number of groups to assign attendees to | |
numGroups = 9 | |
# the number of rounds of assignments to make | |
numRounds = 2 | |
################### START OF SCRIPT ################### | |
currAssignments = [] | |
allPossibleAssignments = list( | |
itertools.permutations([i for i in range(numGroups)], numRounds) | |
) | |
shuffle(allPossibleAssignments) | |
print("Number of possible assignments:", len(allPossibleAssignments)) | |
# fill the assignments list with an initial set of optimal assignments | |
while len(currAssignments) < attendees["max"]: | |
currAssignments += allPossibleAssignments | |
# count the number of attendees in each group for each round | |
numAttendeesPerGroupByRound = [[0 for _ in range(numGroups)] for _ in range(numRounds)] | |
for assignment in currAssignments: | |
for r in range(numRounds): | |
numAttendeesPerGroupByRound[r][assignment[r]] += 1 | |
assignmentsByNumberOfAttendees = {} | |
while len(currAssignments) > attendees["min"]: | |
# find the group with the most attendees for each round | |
groupWithMostAttendeesByRound = [0 for _ in range(numRounds)] | |
for r in range(numRounds): | |
maxAttendees, maxAttendeesGroup = 0, 0 | |
for g in range(numGroups): | |
if numAttendeesPerGroupByRound[r][g] > maxAttendees: | |
# make sure the group wasn't already selected in a previous round | |
shouldContinue = True | |
for i in range(r): | |
if groupWithMostAttendeesByRound[i] == g: | |
shouldContinue = False | |
break | |
if shouldContinue: | |
maxAttendees = numAttendeesPerGroupByRound[r][g] | |
maxAttendeesGroup = g | |
groupWithMostAttendeesByRound[r] = maxAttendeesGroup | |
# attempt to find an assignment matching the round group assingment above | |
foundMatchingAssignment = False | |
for i in range(len(currAssignments)): | |
if all( | |
[ | |
currAssignments[i][r] == groupWithMostAttendeesByRound[r] | |
for r in range(numRounds) | |
] | |
): | |
# remove the assignment | |
for r in range(numRounds): | |
numAttendeesPerGroupByRound[r][currAssignments[i][r]] -= 1 | |
del currAssignments[i] | |
foundMatchingAssignment = True | |
break | |
# if no matching assignment was found, attempt to find an assignment | |
# matching just the first round group assignment | |
if not foundMatchingAssignment: | |
for i in range(len(currAssignments)): | |
if currAssignments[i][0] == groupWithMostAttendeesByRound[0]: | |
# remove the assignment | |
for r in range(numRounds): | |
numAttendeesPerGroupByRound[r][currAssignments[i][r]] -= 1 | |
del currAssignments[i] | |
foundMatchingAssignment = True | |
break | |
# if we still haven't found a matching assignment, remove at random | |
if not foundMatchingAssignment: | |
attendee = choice(currAssignments) | |
for i in range(numRounds): | |
numAttendeesPerGroupByRound[i][attendee[i]] -= 1 | |
currAssignments.remove(attendee) | |
# add the assignments to the dictionary if necessary | |
n = len(currAssignments) | |
if n <= attendees["max"] and n % attendees["interval"] == 0: | |
assignmentsByNumberOfAttendees[len(currAssignments)] = currAssignments.copy() | |
finalAssignments = assignmentsByNumberOfAttendees[attendees["min"]].copy() | |
# update final assignments to include the properly ordered assignments for each | |
# number of attendees | |
for n in range(attendees["min"], attendees["max"] + 1, attendees["interval"]): | |
newAssignments = assignmentsByNumberOfAttendees[n] | |
# find the new assignments | |
for assignment in finalAssignments: | |
if assignment not in newAssignments: | |
print("Error: assignment not found:", assignment) | |
newAssignments.remove(assignment) | |
# add the new assignments | |
finalAssignments += newAssignments | |
print("\nFinal assignments:") | |
for i in range(len(finalAssignments)): | |
print(str(i + 1) + ", " + ", ".join([str(a + 1) for a in finalAssignments[i]])) | |
totGroupSizes = [0 for _ in range(numGroups)] | |
for n in range(attendees["max"], attendees["min"] - 1, -attendees["interval"]): | |
assignments = finalAssignments[:n] | |
print("\nStats for", n, "attendees:") | |
print("- Number of attendees in each group for each round:") | |
for i in range(numRounds): | |
groupSizes = [ | |
len([a for a in assignments if a[0] == i]) for i in range(numGroups) | |
] | |
# keep track of the total number of attendees in each group for GSL rebalancing | |
totGroupSizes = [totGroupSizes[i] + groupSizes[i] for i in range(numGroups)] | |
print(" - Round", i + 1, ":", groupSizes) | |
# print the number of attendees each attendee meets with | |
metWith = [set() for _ in range(n)] | |
for i in range(len(assignments)): | |
groups = assignments[i] | |
for j in range(len(assignments)): | |
if i == j: | |
continue | |
otherGroups = assignments[j] | |
for k in range(numRounds): | |
if groups[k] == otherGroups[k]: | |
metWith[i].add(j) | |
metWith[j].add(i) | |
break | |
numMetWith = [len(m) for m in metWith] | |
print("- Number of attendees each attendee met with:") | |
print(" - Min:", min(numMetWith)) | |
print(" - Max:", max(numMetWith)) | |
# calculate the average group size for each group for GSL rebalancing | |
avgSizePerGroup = [ | |
(i + 1, totGroupSizes[i] / numGroups / numRounds) for i in range(numGroups) | |
] | |
avgSizePerGroup = sorted(avgSizePerGroup, key=lambda x: x[1]) | |
print("\nGroup size ordering (smallest to largest):", [i[0] for i in avgSizePerGroup]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment