20

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?

George
  • 15,241
  • 22
  • 66
  • 83
  • 3
    Assuming you mean `IEnumerable`, but given the question I got an LOL out of `IEnumberable` :) – Yuck Aug 31 '11 at 14:54
  • What is the source of your `IEnumerable`? – Gabe Aug 31 '11 at 14:55
  • The RandomElement function appears to be what you're looking for. http://stackoverflow.com/questions/648196/random-row-from-linq-to-sql/648240#648240 – Raymond Chen Aug 31 '11 at 14:55
  • possible duplicate of [Generate random enum in C# 2.0](http://stackoverflow.com/questions/319814/generate-random-enum-in-c-2-0) – JonH Aug 31 '11 at 14:55
  • 2
    @JonH: No, I don't want to generate a random enum. – George Aug 31 '11 at 14:57
  • @Raymond. The solution you refer to depends on SQL Server while this is pure CLR execution. This is no dupe. – Mikael Östberg Aug 31 '11 at 14:59
  • @Gabe: The source is unimportant. I want to be able to select a random element from any object that implements IEnumberable. – George Aug 31 '11 at 15:01
  • @George: Since your Python code specifically uses a list and your C# code uses an array, I wanted to make sure you weren't looking for an array-/list-specific solution. – Gabe Aug 31 '11 at 15:19
  • @Mikael There is no dependency on SQL in the `RandomElement` function. (Other proposed solutions to the question do use SQL, but `RandomElement` does not.) – Raymond Chen Aug 31 '11 at 15:37
  • @Raymond Oh, I was referring to the accepted answer. – Mikael Östberg Sep 01 '11 at 09:09

4 Answers4

30

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).

David Pine
  • 23,787
  • 10
  • 79
  • 107
Domenic
  • 110,262
  • 41
  • 219
  • 271
  • 3
    Note however that it makes two passes over the data, which can cause problems if the dataset changes in between the call to `Count` and the call to `ElementAt`. (Not an issue here, but may be an issue in general.) – Raymond Chen Aug 31 '11 at 15:35
  • 4
    True; Jon Skeet's answer at http://stackoverflow.com/questions/648196/random-row-from-linq-to-sql/648240#648240 is probably the best one in that regard. – Domenic Aug 31 '11 at 16:00
  • 1
    won't it be a problem if it looks for an ElementAt(enum.Count())? Let's say an enum has 5 items, and the rand chooses 5. Enum.ElementAt(5) will not find anything, right? – AnimaSola Sep 13 '13 at 06:25
  • 2
    `rand.Next` never chooses its upper bound. – Domenic Sep 13 '13 at 22:04
4

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.

Gabe
  • 84,912
  • 12
  • 139
  • 238
Timothy Khouri
  • 31,315
  • 21
  • 88
  • 128
  • What do you mean "semi-random"? – NullUserException Aug 31 '11 at 14:55
  • 3
    The random class generates pseudo-random numbers, not cryptographically strong random numbers. – Chris Shain Aug 31 '11 at 14:56
  • 1
    @Chris I know what pseudo-random is. I don't know what "semi-random" is supposed to mean. And being pseudo-random doesn't preclude it from being cryptographically secure, eg: `cryptographically strong != (!pseudo-random)` – NullUserException Aug 31 '11 at 14:58
  • It may be easy, but it is expensive since it sorts the entire enumeration. There are well-known one-pass algorithms for random selection which operate incrementally on the enumeration. http://stackoverflow.com/questions/48087/select-a-random-n-elements-from-listt-in-c – Raymond Chen Aug 31 '11 at 14:59
  • Pseudo-random != cryptographically strong. If you can guess the value, you can crack cryptography based on it. From http://en.wikipedia.org/wiki/Strong_cryptography: "This term cryptographically strong is often used to describe an encryption algorithm, and implies, in comparison to some other algorithm (which is thus cryptographically weak), greater resistance to attack. .... In this usage, the term means difficult to guess." – Chris Shain Aug 31 '11 at 15:01
  • @Chris: Wrong. `pseudo-random = !(true random)`, and you can't get true randomness with deterministic computers unless you have specialized hardware (and even then, the randomness is debatable). Wiki on [CSPRNG](http://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator). – NullUserException Aug 31 '11 at 15:02
  • It is random in the sense of not biased towards any particular value, yes. – Chris Shain Aug 31 '11 at 15:03
4

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()));
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
-1

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!?

fredw
  • 1,409
  • 12
  • 23
  • 7
    [] isn't going to work with IEnumerable, which is what the poster asked for. I don't think the poster wanted an array or List based solution, which as you point out, is straightforward. – Corey Kosak Aug 31 '11 at 15:11