39

I am trying to get a random object within linq. Here is how I did.

//get all the answers
var Answers = q.Skip(1).Take(int.MaxValue);
//get the random number by the number of answers
int intRandomAnswer = r.Next(1, Answers.Count());
int count = 0;

//locate the answer
foreach(var Answer in Answers)
{
    if (count == intRandomAnswer)
    {
        SelectedPost = Answer;
        break;
    }
    count++;
}

Is this the best way to do this?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Luke101
  • 63,072
  • 85
  • 231
  • 359
  • 2
    Google led me to this post, but the accepted answer is misleading (in combination with the question), because it does not return the first and last element at all (I am aware that this might be what you looked for), but given that the question title and brief desciption is about finding *a* random element, I suggest you change the wording of the question (and/or title) to reflect on this :) – Jonathan Sep 03 '15 at 06:11
  • @Rouby I think you have enough rep to change – Luke101 Aug 25 '17 at 14:44

9 Answers9

51

What about:

SelectedPost = q.ElementAt(r.Next(1, Answers.Count()));

Further reading:

The comments below make good contributions to closely related questions, and I'll include them here, since as @Rouby points out, people searching for an answer to these may find this answer and it won't be correct in those cases.

Random Element Across Entire Input

To make all elements a candidate in the random selection, you need to change the input to r.Next:

SelectedPost = Answers.ElementAt(r.Next(0, Answers.Count()));

@Zidad adds a helpful extension method to get random element over all elements in the sequence:

public static T Random<T>(this IEnumerable<T> enumerable)
{
    if (enumerable == null)
    {
         throw new ArgumentNullException(nameof(enumerable));
    }

    // note: creating a Random instance each call may not be correct for you,
    // consider a thread-safe static instance
    var r = new Random();  
    var list = enumerable as IList<T> ?? enumerable.ToList(); 
    return list.Count == 0 ? default(T) : list[r.Next(0, list.Count)];
}
codekaizen
  • 26,990
  • 7
  • 84
  • 140
  • This is awesome..Let me ask where did you learn c#? What books did you read? – Luke101 Jul 04 '10 at 05:04
  • Been so long, I don't remember. I just code these days. However, I'd recommend C# 4.0 by Joseph Albahari (http://www.albahari.com/nutshell/) and CLR via C# by Jeffrey Richter (http://www.amazon.com/CLR-Via-C-Pro-Developer/dp/0735621632). – codekaizen Jul 04 '10 at 05:10
  • 5
    Oh, and I understand some guy by the name of Jon Skeet might know a thing or two about C# and wrote a book about it. ;) – codekaizen Jul 04 '10 at 05:14
  • 2
    If you're trying to find a generic solution, this method will never return the first element :) `public static T Random(this IEnumerable enumerable) { var r = new Random(); var list = enumerable as IList ?? enumerable.ToList(); return list.ElementAt(r.Next(0, list.Count())); }` – Wiebe Tijsma Jan 25 '14 at 13:31
  • Isent linq 0 based and not 1 based, this solution would never get the first item? – Peter Feb 06 '15 at 21:44
  • That's what the question wanted - random after the first. – codekaizen Feb 07 '15 at 01:27
  • 1
    @Rouby - Thanks for the insight, I'm sure it will be helpful to some readers. However, it's not wrong. As I've mentioned to others in this comment thread, I'm only answering the given question narrowly. I'm reusing the code posted, focusing only on the question asked, since nothing else was specified, and assuming intent has its own pitfalls. – codekaizen Sep 02 '15 at 21:35
  • @codekaizen Fair enough, I think I overlooked that. I proposed a change to the question, since google led me here in search for a *true* random element from a list and I might think others get led here to, and just skim the original question (like I did) and may assume your answer gives them what they need :) I retracted my old comment – Jonathan Sep 03 '15 at 05:32
  • You should remove the first part of this answer, as it is the first thing people see. Some will incorrectly assume that it is your actual answer. (I made the same mistake initially.) As an example, see this [other answer](http://stackoverflow.com/a/22983031/1822514) below. – chue x Oct 08 '15 at 19:57
  • @chuex - but that's what actually answers the question. Further, it's one line to read, and the rest of the answer isn't long. Programming and getting the right answer sometimes means you have to read the whole answer. – codekaizen Oct 08 '15 at 20:00
  • You're right, you do answer the question. I guess it is the question that could be clarified, as [Rouby has already pointed out](http://stackoverflow.com/questions/3173718/how-to-get-a-random-object-using-linq/3173726#comment52608131_3173718). – chue x Oct 08 '15 at 20:21
14

Another wacky approach (not the most efficient for larger data sets):

SelectedPost = q.OrderBy(qu => Guid.NewGuid()).First();
BFree
  • 102,548
  • 21
  • 159
  • 201
10

Use a Fisher-Yates-Durstenfeld shuffle.

(You could use a helper/extension method to shuffle your IEnumerable<T> sequence. Alternatively, if you were using an IList<T> you could perform an in-place shuffle, if you prefer.)

Community
  • 1
  • 1
LukeH
  • 263,068
  • 57
  • 365
  • 409
  • You can do this shuffle without another array by starting the index at the end and randomly selecting an index of the elements before the current index, and swapping the values. This continues in a loop, decrementing the current index, until the beginning is reached. – codekaizen Jan 28 '12 at 23:02
  • 1
    @codekaizen: You can do that if your sequence is indexable (ie, if it's really an `IList` of some kind). You can't guarantee that any arbitrary `IEnumerable` will be indexable though. – LukeH Jan 29 '12 at 21:21
4
var rand = new Random();
var selectedPost = q.Skip(rand.Next(0, q.Count())).Take(1).FirstOrDefault();

Optimally, you want to only ever make the function query for a single value, so you set up the Skip/Take to jump up to the sequence number matching the random number you're generating (bounded by dataset's itemcount, so the missing row problem bounding based on MAX(pkey) isn't an issue) and then snag the first item at that point in the sequence.

In SQL this is the same as querying for SELECT Count(*) FROM q, then SELECT * FROM q LIMIT {0}, 1 where {0} is rand.Next(0, count), which should be pretty efficient.

Hein Andre Grønnestad
  • 6,885
  • 2
  • 31
  • 43
Garandy
  • 203
  • 1
  • 10
4

Generic extension method based on the accepted answer (which doesn't always skip the first, and only enumerates the enumerable once):

 public static class EnumerableExtensions
    {
        public static T Random<T>(this IEnumerable<T> enumerable)
        {
            var r = new Random();
            var list = enumerable as IList<T> ?? enumerable.ToList();
            return list.ElementAt(r.Next(0, list.Count()));
        }
    }
Wiebe Tijsma
  • 10,173
  • 5
  • 52
  • 68
  • Nice and neat, however the line 'var r = new Random();' should be moved up to become a static class member. If not, rapid calls to the method will generate the same result. – Will Jenkins Jun 10 '14 at 18:50
  • 1
    Aha thanks for that. For my purposes accidently returning the same random number in case they are executed exactly at the same time is not a big issue. However beware of a static Random instance because Next() is not thread safe and might break. [ThreadStatic] might work or a Thread safe Random() solution like this: http://stackoverflow.com/a/11109361/67910 – Wiebe Tijsma Jun 11 '14 at 15:05
4

Late to the party but this is a high-up Google result. A succinct version could be:

var rnd = new Random();
var SelectedPost = q.OrderBy(x => rnd.Next()).Take(1);

It has the disadvantage that it'll apply a random number to all elements, but is compact and could easily be modified to take more than one random element.

Dan
  • 1,130
  • 2
  • 20
  • 38
2

Pulling all of the answers and looping them isn't the most efficient way as you're moving lots of data from the database. If you're using an integer primary key that's automatically incrementing, you should get the Max of your primary key and then find the random integer within that range. Then directly get the single answer based on the primary key derived from the random function.

Turnkey
  • 9,266
  • 3
  • 27
  • 36
  • somewhat assumes that particular one is still around, but you could query for the row that has max(pk) <= choice – James Manning Jul 04 '10 at 05:27
  • Good point, there could be some that have been deleted or where the pk is not monotonic within that range, probably best to get the range out of a subquery to ensure that it exists. – Turnkey Jul 04 '10 at 13:40
1

I'm posting an answer because I don't have enough reputation to comment.

I like this answer:

SelectedPost = q.ElementAt(r.Next(1, Answers.Count()));

But ElementAt is zero based, surely starting at 1 and going to Answers.Count() you are going to end up potentially throwing an out of range, and you are never going to get the first entity.

Wouldn't

SelectedPost = q.ElementAt(r.Next(0, Answers.Count() - 1));

Be better?

Tod
  • 2,070
  • 21
  • 27
  • 3
    The [upper bound to `Next` is exclusive](http://msdn.microsoft.com/en-us/library/2dx6wyd4%28v=vs.110%29.aspx) which means now you are missing the last element. Correct would be `Next(0, Answers.Count())` – ChrisWue Jan 06 '15 at 20:24
  • Also note the question was random *after the first*, thus the start at index 1. – codekaizen Jun 17 '15 at 09:24
  • 2
    I never realised the random next was upper exclusive, seems kinda daft imho. I don't see anywhere in the first question any mention of miss first, other than that's what his code would do, but that doesn't mean it should. – Tod Jun 22 '15 at 08:36
  • This should be a comment to the original answer. – Leandro Bardelli Jul 05 '22 at 18:10
0

I have product table in database ,every time user enters one product detail I want to show 10 similar products in below of page.And in every refresh this list must be change .it must come randomly.

Linq looks like this

var products =
            DataContextFactory.GetDataContext()
                .Set<Product>()
                .Where(x =>x.Id!=id)
                .OrderBy(emp => Guid.NewGuid())
                .Take(10).ToList();

x.Id!=id 

this only for not put selected product to list .

It works perfect

RASKOLNIKOV
  • 732
  • 2
  • 9
  • 20