Skip to main content

Fisher-Yates

TL;DR

Use this algorithm if you want to preserve the order of the winners.

This algorithm is the modern Fisher-Yates shuffle algorithm as described here.

Solidity
function algorithmFisherYatesOptimized(bytes totalEntropy, uint32 nbParticipants, uint32 nbWinners) private returns (uint32[] memory)
{
uint32[] memory winnerIndexes = new uint32[](nbWinners); // Fixed sized array, all elements initialize to 0
uint32 from = 0;
mapping(uint32 => uint32) swapMap;

for (uint32 i = 0; i < nbWinners; i++) {
bytes8 extractedEntropy = extractBytes8(totalEntropy, from);
from += entropyNeededPerWinner;

// When i winners are already selected, we only need a random number between 0 and nbParticipants - i - 1 to select the next winner.
// ⚠️ Using 64-bit integers for the modulo operation is extremely important to prevent scaling bias ⚠️
// Then it is fine to convert the result to a 32-bit integer because we know that the output of the modulo will always be stricly less than nbParticipants which is a 32-bit integer
uint32 randomNumber = uint32(uint64(extractedEntropy) % uint64(nbParticipants - i));

winnerIndexes[i] = swapMap[randomNumber] ? swapMap[randomNumber] : randomNumber;
swapMap[randomNumber] = nbParticipants - i - 1;
}

// We want to display line numbers, not indexes, so all indexes need to be +1
for (uint32 i = 0; i < nbWinners; i++) {
winnerIndexes[i] += 1;
}

return winnerIndexes;
}