alwaysbegrowing / telegram-standup-bot

Very simple telegram bot for submitting daily standups
https://stoodbot.vercel.app
MIT License
27 stars 12 forks source link

Make lottery less random #79

Open Namaskar-1F64F opened 2 years ago

Namaskar-1F64F commented 2 years ago

A request for a less random picker was brought up after some people out of a group of 15 participants have yet to be picked in the past two months.

Describe the solution you'd like Have a new list of weights per participant. the weights can start proportionally and when a person is chosen, their weight is cut by 3/4 and their weight is evenly distributed to the other participants. When a new participant joins, weights are adjusted to allow the newcomer a starting proportional weight.

Describe alternatives you've considered to start - make sure a person isn't chosen twice in a row by filtering out the previous winners. https://github.com/alwaysbegrowing/telegram-standup-bot/blob/805c853ca284219e69f2780a04c7493ee854210e/pages/api/lib/_lottery.ts#L18

Additional context Maybe something like this? https://www.npmjs.com/package/random-weighted-choice where everyone but the winner's weight is increased by 1? and newcomer weight is the average of all weights?

Geczy commented 1 year ago

Your proposed solution to use weighted random selection with weights adjusted based on previous selections seems reasonable. It would allow for more even distribution of selections among participants over time and prevent certain participants from being picked too frequently or too infrequently.

As for the implementation, you could create an object to store the current weights for each participant and initialize them with proportional weights based on the number of participants. For example, if you have 15 participants, each would have an initial weight of 1/15. Then, when a participant is chosen, their weight would be cut by 3/4 and their weight would be evenly distributed to the other participants, meaning each of the remaining participants would receive an additional weight of 3/(4*(n-1)), where n is the number of participants remaining after removing the selected participant. When a new participant joins, you would adjust the weights again to allow the newcomer a starting proportional weight.

Here's some sample code that implements this approach:

const participants = ['Alice', 'Bob', 'Charlie', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy', 'Kathy', 'Larry', 'Mallory', 'Nancy', 'Oscar'];
const weights = {};
let remainingParticipants = [...participants];

// Initialize weights with proportional values
remainingParticipants.forEach(p => {
  weights[p] = 1 / participants.length;
});

// Function to select a participant
function selectParticipant() {
  if (remainingParticipants.length === 0) {
    console.log('No participants remaining!');
    return null;
  }

  // Calculate cumulative weights
  const cumWeights = {};
  let cumWeight = 0;
  remainingParticipants.forEach(p => {
    cumWeight += weights[p];
    cumWeights[p] = cumWeight;
  });

  // Generate random number in range [0,1)
  const rand = Math.random();

  // Select participant with corresponding cumulative weight
  const selectedParticipant = remainingParticipants.find(p => cumWeights[p] > rand * cumWeight);

  // Update weights
  remainingParticipants = remainingParticipants.filter(p => p !== selectedParticipant);
  const remainingWeight = 3 / (4 * remainingParticipants.length);
  remainingParticipants.forEach(p => {
    weights[p] += remainingWeight;
  });
  weights[selectedParticipant] *= 1 / 4;

  return selectedParticipant;
}