def test_all_students_paired_up_with_even_amount_of_students( self, num_students): teams = constants.STUDENTS[:num_students] allocations = pairwise.generate_review_allocations(teams, num_reviews=1) assert len(allocations) == num_students for review_team, reviewed_team in allocations: expected_counter_review = plug.ReviewAllocation( review_team=reviewed_team, reviewed_team=review_team) assert allocations.index(expected_counter_review) >= 0
def generate_review_allocations( teams: List[plug.StudentTeam], num_reviews: int = 1 ) -> List[plug.ReviewAllocation]: """Generate peer review allocations such that if team_a reviews team_b, then team_b reviews team_a, and no others! The ``num_reviews`` argument is ignored by this plugin. Args: teams: Student teams for which to allocate reviews. num_reviews: Ignored by this plugin. Returns: A list of allocations that """ teams = list(teams) if num_reviews != 1: plug.log.warning( f"num_reviews specified to {num_reviews}, but in pairwise assignment " f"num_reviews is ignored" ) if len(teams) < 2: raise ValueError( f"there must be at least 2 teams for peer review, " f"but {len(teams)} were provided" ) random.shuffle(teams) groups = [(teams[i - 1], teams[i]) for i in range(1, len(teams), 2)] # odd number of teams necessitates 3 teams in last group last_review_group = ( (*groups[-1], teams[-1]) if len(teams) % 2 else groups[-1] ) finalized_groups = [*groups[:-1], last_review_group] allocations = [] for group in finalized_groups: for i, review_team in enumerate(group): reviewed_team = group[(i + 1) % len(group)] allocations.append( plug.ReviewAllocation( review_team=review_team, reviewed_team=reviewed_team ) ) return allocations
def generate_review_allocations( teams: List[plug.StudentTeam], num_reviews: int ) -> List[plug.ReviewAllocation]: if num_reviews >= len(teams): raise ValueError("num_reviews must be less than len(teams)") if num_reviews <= 0: raise ValueError("num_reviews must be greater than 0") if len(teams) < 2: raise ValueError( f"there must be at least 2 teams for peer review, " f"but {len(teams)} were provided" ) random.shuffle(teams) # create a list of lists, where each non-first list is a left-shifted # version of the previous list (lists wrap around) # e.g. for teams [4, 3, 1] and num_reviews = 2, the result is # allocations = [[4, 3, 1], [3, 1, 4], [1, 4, 3]] and means that # student 4 gets reviewed by 3 and 1, 3 by 1 and 4 etc. allocations = [teams] for _ in range(num_reviews): next_reviewers = list(allocations[-1]) next_reviewers.append(next_reviewers.pop(0)) # shift list left allocations.append(next_reviewers) def merge_teams(teams): members = list( itertools.chain.from_iterable([team.members for team in teams]) ) return plug.StudentTeam(members=members) transposed_allocations = zip(*allocations) review_allocations = [ plug.ReviewAllocation( review_team=merge_teams(reviewers), reviewed_team=reviewed_team ) for reviewed_team, *reviewers in transposed_allocations ] return review_allocations