Skip to content

Commit

Permalink
Try to avoid pairing the same users again
Browse files Browse the repository at this point in the history
  • Loading branch information
justinyaodu committed Nov 5, 2023
1 parent 63c866e commit 52f9940
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 11 deletions.
4 changes: 3 additions & 1 deletion src/models/GroupModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,13 @@ const GroupSchema = new Schema<Group>({
},
});

const indexes: { [K in keyof Group]?: 1 }[] = [
const indexes: { [K in keyof Group]?: 1 | -1 }[] = [
// Used to determine what scheduled messages should be sent.
{ round: 1, initialMessageTimestamp: 1, status: 1 },
{ round: 1, reminderMessageTimestamp: 1, status: 1 },
{ round: 1, finalMessageTimestamp: 1, status: 1 },
// Used to find the most recent groups in a particular channel.
{ channel: 1, initialMessageTimestamp: -1 },
];
indexes.forEach((index) => GroupSchema.index(index));

Expand Down
59 changes: 49 additions & 10 deletions src/services/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { App } from "@slack/bolt";
import mongoose from "mongoose";

import env from "../env";
import { GroupModel } from "../models/GroupModel";
import { Group, GroupModel } from "../models/GroupModel";
import { RoundDocument } from "../models/RoundModel";
import { Result } from "../util/result";

Expand All @@ -29,6 +29,12 @@ async function getUsersToMatch(
return Result.ok(filtered);
}

async function getPreviousPairings(channel: string): Promise<Group[]> {
return GroupModel.find({ channel: channel })
.sort({ initialMessageTimestamp: -1 })
.limit(50);
}

/**
* @returns A shuffled shallow copy of the input array.
*/
Expand All @@ -51,9 +57,44 @@ function shuffled<T>(values: T[]): T[] {
* Pair users randomly, including a group of three if there is an odd number, or
* a group of one if there is only one user.
*/
function makeGroups(users: string[]): string[][] {
function makeGroups(users: string[], prevGroups: Group[]): string[][] {
if (users.length === 0) {
return [];
} else if (users.length <= 3) {
return [users];
}

users = shuffled(users);

// Attempt to avoid pairing users who have been paired previously.

const prevPairs = new Set<string>();
for (const group of prevGroups) {
for (let i = 0; i < group.userIds.length; i++) {
for (let j = 0; j < group.userIds.length; j++) {
if (i != j) {
prevPairs.add(group.userIds[i] + group.userIds[j]);
}
}
}
}

for (let iteration = 0; iteration < 20; iteration++) {
// Iterate over all pairs. There might be one extra person at the end, who
// becomes part of a group of three, but we ignore them for simplicity.
for (let i = 0; i + 1 < users.length; i += 2) {
if (prevPairs.has(users[i] + users[i + 1])) {
// Users have been paired before - swap one of them with someone else.
const src = i + Math.floor(Math.random() * 2);
const dest = Math.floor(Math.random() * users.length);

const temp = users[src];
users[src] = users[dest];
users[dest] = temp;
}
}
}

const groups: string[][] = [];

// Make as many pairs as possible.
Expand All @@ -65,13 +106,8 @@ function makeGroups(users: string[]): string[][] {

// Is there an unpaired person?
if (i < users.length) {
if (groups.length === 0) {
// Only one person - put them in a group by themself.
groups.push([users[i]]);
} else {
// Add the person to the first group.
groups[0].push(users[i]);
}
// Add the person to the first group.
groups[0].push(users[i]);
}

return groups;
Expand All @@ -90,7 +126,10 @@ async function createGroups(
return Result.err(`could not get users to match: ${usersResult.error}`);
}

const groups = makeGroups(usersResult.value);
const groups = makeGroups(
usersResult.value,
await getPreviousPairings(round.channel),
);

await mongoose.connection.transaction(async () => {
await GroupModel.insertMany(
Expand Down

0 comments on commit 52f9940

Please sign in to comment.