Note that we've now called two methods which iterate partially or fully through values
. If it was a non-deterministic iterator we have problems. An example of a non-deterministic iterator would be
public IEnumerable<int> Demo()
{
var rnd = new Random();
for (int i = rnd.Next(10); i > 0; i--) yield return rnd.Next();
}
If you call this once and then loop through the resulting iterator multiple times then it will not consistently return the same values. You may think that in real usage you won't come across iterators like that, but sometimes the non-determinism is a lot more subtle.
Finally, on the names. In your context it might be obvious that Sample
means "sample without replacement", but in some contexts it might be important to distinguish between sampling with and without replacement. Consider whether you might need to distinguish the cases in future usage of the library.
Postscript: t3chb0t's suggestion of a Randomise
method which can then be combined with Take
is compatible with a rewrite of your GetSamples
method, and in fact this is what I have kicking around in my library of utility methods (although I call it Shuffle
). As a modification of your code:
public static IEnumerable<T> Randomise<T>(IEnumerable<T> values, Random rnd = null)
{
if (rnd == null) rnd = _rnd;
var source = new List<T>(values);
while (source.Count > 0)
{
var index = rnd.Next(source.Count);
yield return source[index];
source[index] = source[source.Count - 1];
source.RemoveAt(source.Count - 1);
}
}