You can use Reservoir Sampling to make a single pass through a sequence of unknown size to select a random set of N items:
public static List<T> RandomlyChooseItems<T>(IEnumerable<T> items, int n, Random rng)
{
var result = new List<T>(n);
int index = 0;
foreach (var item in items)
{
if (index < n)
{
result.Add(item);
}
else
{
int r = rng.Next(0, index + 1);
if (r < n)
result[r] = item;
}
++index;
}
return result;
}
You use it like this:
IEnumerable<Question> questions = ...;
Random rng = new Random();
int questionCount = rng.Next(1, 5); // Get between 1 and 4 questions.
List<Question> randomQuestions = RandomlyChooseItems(questions, questionCount, rng);
This isn't usually much more efficient than copying the items into an array and doing a Fisher-Yates shuffle
, but can be useful if the size of the list to choose from is very large and the number of items to choose is relatively small, since you only need to store the chosen items and not the original list of items.