Skip to content

Instantly share code, notes, and snippets.

@chappyasel
Created April 8, 2023 14:53
Show Gist options
  • Save chappyasel/cac568706875e73ecd1f6f6d6fd7db7f to your computer and use it in GitHub Desktop.
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
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