Based on algorithm from the "Real world functional programming" book here's the O(n) implementation of extension method for taking random or default value from IEnumearble.
public static class SampleExtensions
{
// The Random class is instantiated as singleton
// because it would give bad random values
// if instantiated on every call to RandomOrDefault method
private static readonly Random RandomGenerator = new Random(unchecked((int)DateTime.Now.Ticks));
public static T RandomOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
IEnumerable<T> filtered = source.Where(predicate);
int count = 0;
T selected = default(T);
foreach (T current in filtered)
{
if (RandomGenerator.Next(0, ++count) == 0)
{
selected = current;
}
}
return selected;
}
public static T RandomOrDefault<T>(this IEnumerable<T> source)
{
return RandomOrDefault(source, element => true);
}
}
Here's the code to ensure that this algorithm really gives the Uniform distribution:
[Test]
public void TestRandom()
{
IEnumerable<int> source = Enumerable.Range(1, 10);
Dictionary<int, int> result = source.ToDictionary(element => element, element => 0);
result[0] = 0;
const int Limit = 1000000;
for (int i = 0; i < Limit; i++)
{
result[source.RandomOrDefault()]++;
}
foreach (var pair in result)
{
Console.WriteLine("{0}: {1:F2}%", pair.Key, pair.Value * 100f / Limit);
}
Console.WriteLine(Enumerable.Empty<int>().RandomOrDefault());
}
The output of TestRandom method is:
1: 9,92%
2: 10,03%
3: 10,04%
4: 9,99%
5: 10,00%
6: 10,01%
7: 9,98%
8: 10,03%
9: 9,97%
10: 10,02%
0: 0,00%
0