Given a list of reviewers and a list of pull requests. Need to design an algorithm to assign reviewers in a round-robin approach. After each iteration, the review history is stored(Selecting a reviewer with the least review history won't work.). Need to make sure that new engineers (newly hired) won't be the only reviewers for the upcoming pull requests as they have the least number of reviewers.
At this moment, I don't have any code just trying to find an approach how to solve a problem with possibly a new reviewer with zero review count. Implementing an algorithm with a static list of reviewers would be a problem.
Here's the specific use case that i'm trying to solve:
review_history = [{'user_1': 10}, {'user_2': 8}, {'user_3': 9}, {'user_4': 0}]
- if select a user with the least review count, the user_4 will be selected for the next 8 pull requests.
- if select a user randomly the review count won't be distributed equally.
Any ideas would be appreciated. Thanks!
CodePudding user response:
Maybe you should just assign in phases based on priority. For example:
Start by assigning any reviewers requested for a specific task to that task, unless some max # of review limit is reached for a reviewer.
Take all the non-new reviewers and start spreading them around the tasks. You should keep track of which tasks have the least number of reviewers, and only round robin or randomly select a task from that subset for each reviewer. You could also only select from a group of reviewers that have the least number of assigned tasks, so you will keep the workload balanced. Once you assign each underutilized reviewer to a least assigned review task, the tasks reviewer count will increment up by one so it will no longer be the least reviewed... unless of course there are no more less-reviewed tasks. Continue to loop over the reviewers with the least number of assignments, and keep assigning them only to the tasks with the least number of reviewers. Don't consider any historical reviews, as people who are added later only need to get an even workload for the current iteration.
Since new reviewers should not be primary reviewers, assign them last. Again, maybe loop over each new reviewer one at a time, and only assign them to the set of tasks with the least number of reviewers.
You could probably keep track of all this in Python using dictionaries. For example, for each task, you could have a sub dictionary containing the ids of all the assigned viewers. You could count how many keys are in those dictionaries to determine which task had the least number of assigned reviewers, or just create another data structure to keep track of the counts. You could also have a dictionary of reviewer ids to keep track of how many tasks they were assigned to, in case there's a limit for how many reviews they can be assigned.
Would be curious if anyone has any better ideas... I don't think this problem needs a super complex algorithm though to get a decent solution.
CodePudding user response:
Maybe you could just use a queue for that.
- The idea is to have a queue of IDs or the reviewer-instances directly, that starts in some random or organized order (e.g. alphabetically).
- Then you first assign the specific tasks by removing the reviewer from the queue and appending him to the back of the queue one by one.
- After all the specific tasks are assigned you take the next reviewer from the queue assign him the next task and put him to the back of the queue.
- New reviewers could be appended to the front of the queue so they'll get the next task that is not specific but then go to the back of the queue after.
In the easiest form you can implement a queue as a deque in python, that has all functionality you need (append, appendleft, pop and remove).
from collections import deque
specific_tasks = [(3, "math"), (5, "english"), (2, "biology")]
print(f"{specific_tasks=}")
other_tasks = ["geography", "physiks", "chemistry", "sport", "history", "german"]
print(f"{other_tasks=}")
queue = deque([1, 2, 3, 4, 5, 6])
print(f"{queue=}")
print("\n--- assign specific tasks ---")
for reviewer_id, specific_task in specific_tasks:
queue.remove(reviewer_id)
queue.appendleft(reviewer_id)
print(f"assign {specific_task=} to {reviewer_id=}")
print(f"{queue=}")
print("\n--- assign other tasks ---")
for other_task in other_tasks:
reviewer_id = queue.pop()
queue.appendleft(reviewer_id)
print(f"assign {other_task=} to {reviewer_id=}")
print(f"{queue=}")
print("\n--- add reviewer ---")
new_reviewer = 9
queue.append(new_reviewer)
print(f"add reviewer {new_reviewer}")
print(f"{queue=}")
print("\n--- remove reviewer ---")
reviewer_to_remove = 4
queue.remove(reviewer_to_remove)
print(f"remove reviewer {reviewer_to_remove}")
print(f"{queue=}")
This gives following output:
specific_tasks=[(3, 'math'), (5, 'english'), (2, 'biology')]
other_tasks=['geography', 'physiks', 'chemistry', 'sport', 'history', 'german']
queue=deque([1, 2, 3, 4, 5, 6])
--- assign specific tasks ---
assign specific_task='math' to reviewer_id=3
queue=deque([3, 1, 2, 4, 5, 6])
assign specific_task='english' to reviewer_id=5
queue=deque([5, 3, 1, 2, 4, 6])
assign specific_task='biology' to reviewer_id=2
queue=deque([2, 5, 3, 1, 4, 6])
--- assign other tasks ---
assign other_task='geography' to reviewer_id=6
queue=deque([6, 2, 5, 3, 1, 4])
assign other_task='physiks' to reviewer_id=4
queue=deque([4, 6, 2, 5, 3, 1])
assign other_task='chemistry' to reviewer_id=1
queue=deque([1, 4, 6, 2, 5, 3])
assign other_task='sport' to reviewer_id=3
queue=deque([3, 1, 4, 6, 2, 5])
assign other_task='history' to reviewer_id=5
queue=deque([5, 3, 1, 4, 6, 2])
assign other_task='german' to reviewer_id=2
queue=deque([2, 5, 3, 1, 4, 6])
--- add reviewer ---
add reviewer 9
queue=deque([2, 5, 3, 1, 4, 6, 9])
--- remove reviewer ---
remove reviewer 4
queue=deque([2, 5, 3, 1, 6, 9])