George
George

Reputation: 15551

C#: Elegant code for getting a random value from an IEnumerable

In Python, I can do this:

>>> import random
>>> ints = [1,2,3]
>>> random.choice(ints)
3

In C# the first thing I did was:

var randgen = new Random();
var ints = new int[] { 1, 2, 3 };
ints[randgen.Next(ints.Length)];

But this requires indexing, also the duplication of ints bothers me. So, I came up with this:

var randgen = new Random();
var ints = new int[] { 1, 2, 3 };
ints.OrderBy(x=> randgen.Next()).First();

Still not very nice and efficient. Is there a more elegant way of getting a random value from an IEnumberable?

Upvotes: 20

Views: 28665

Answers (4)

Domenic
Domenic

Reputation: 112837

Here's a couple extension methods for you:

public static T RandomElement<T>(this IEnumerable<T> enumerable)
{
    return enumerable.RandomElementUsing<T>(new Random());
}

public static T RandomElementUsing<T>(this IEnumerable<T> enumerable, Random rand)
{
    int index = rand.Next(0, enumerable.Count());
    return enumerable.ElementAt(index);
}

// Usage:
var ints = new int[] { 1, 2, 3 };
int randomInt = ints.RandomElement();

// If you have a preexisting `Random` instance, rand, use it:
// this is important e.g. if you are in a loop, because otherwise you will create new
// `Random` instances every time around, with nearly the same seed every time.
int anotherRandomInt = ints.RandomElementUsing(rand);

For a general IEnumerable<T>, this will be O(n), since that is the complexity of .Count() and a random .ElementAt() call; however, both special-case for arrays and lists, so in those cases it will be O(1).

Upvotes: 32

Chris Shain
Chris Shain

Reputation: 51339

Sorting will be far less efficient. Just use Skip(n) and First():

var randgen = new Random();
var ints = new int[] { 1, 2, 3};

ints.Skip(x=> randgen.Next(0, ints.Count())).First();

ints.ElementAt(x=> randgen.Next(0, ints.Count()));

Upvotes: 4

Timothy Khouri
Timothy Khouri

Reputation: 31845

No, that's basically the easiest way. Of course, that's only semi-random, but I think it fits most needs.

EDIT: Huge Point Here...

If you only want ONE value randomly chosen from the list... then just do this:

var myRandomValue = ints[(new Random()).Next(0, ints.Length)];

That's a O(1) operation.

Upvotes: 4

fredw
fredw

Reputation: 1419

How about something simple and readable:

ints[randgen.Next(ints.Length)];

Seriously, why obfuscate your code with lambdas .OrderBy and .First and .Skip and so forth!?

Upvotes: -1

Related Questions