1

I build my MyObject array with :

    MyObject[] myObject = (from MyObject varObj in MyObjects
                           select varObj).ToArray();

and now, I'd like to extract 3 random MyObject from this array! How can I do it on C#?

Of course, if array lenght is <3 I need to extract all objects!

markzzz
  • 47,390
  • 120
  • 299
  • 507

6 Answers6

8

You can do this via Linq:

  1. Retrieve the items in random order (see Jon Skeet's answer to this SO question)
  2. Select Top(3) of the resulting list using the Take operator

As an example, select 3 processes at random:

var ps = (from p in Process.GetProcesses() orderby Guid.NewGuid() select p).Take(3);

You can also use random.Next() instead of Guids (since strictly speaking, as pointed out by LukeH, Guids are unique, but not random).

Community
  • 1
  • 1
Philipp Schmid
  • 5,778
  • 5
  • 44
  • 66
  • This is really elengant! How can I get top(3) on linq? Don't know how to do the random order on linq... – markzzz Jul 21 '11 at 13:46
  • Yeah, I am still learning to think in terms of sets (like the SQL guys do it) rather than procedural processing. – Philipp Schmid Jul 21 '11 at 13:49
  • 2
    This solution is elegant in terms of code but not in terms of performance. Problem is that it will sort whole array, which is not necessary at all. – Andrey Jul 21 '11 at 13:50
  • Make sure you read Jon Skeet's answer to the linked SO question. Basically, order by Guid.NewGuid() as part of your Linq query. – Philipp Schmid Jul 21 '11 at 13:52
  • 2
    @Philipp Schmid it is such a waste of performance to generate Guids for each element just to shuffle them. – Andrey Jul 21 '11 at 13:58
  • Agreed. But sometimes the simplicity (and correctness) of a solution is more important than a few milliseconds of performance gains. – Philipp Schmid Jul 21 '11 at 14:24
  • @Philipp: That's true, but ordering by GUIDs is *not* correct. GUIDs are guaranteed to be *unique*, not random. – LukeH Jul 21 '11 at 14:36
  • 1
    @Philipp Schmid sometimes yes, sometimes not. If you run this on server lots of times then you will start bothering about milliseconds. – Andrey Jul 21 '11 at 14:46
3
        MyObject[] myObject = ...;
        int upper = 1;
        if (myObject.Length > 1)
        {
            Random r = new Random();
            upper = Math.Min(3, myObject.Length);
            for (int i = 0; i < upper; i++)
            {
                int randInd = r.Next(i, myObject.Length);
                MyObject temp = myObject[i];
                myObject[i] = myObject[randInd];
                myObject[randInd] = temp;
            }
        }

now take elements of the array from 0 to upper.

Andrey
  • 59,039
  • 12
  • 119
  • 163
  • Yeah, this is the solution I'd prefeer :=) Thanks dude – markzzz Jul 21 '11 at 14:01
  • @markzzz I guarantee that my solution is best in terms of performance (`O(3)`) and in terms of memory (`O(1)`) among others. The problem is that solution that has most votes has dreadful performance and a lot of waste of memory. – Andrey Jul 21 '11 at 14:03
  • Good in principle, but shouldn't that be `int randInd = r.Next(i, myObject.Length);` otherwise the last element is never going to be included in the shuffle. – LukeH Jul 21 '11 at 14:42
  • And also worth mentioning that this will mutate the original array as opposed to just returning *n* random elements and leaving the original array alone. – LukeH Jul 21 '11 at 14:43
  • @LukeH it is mentioned in last sentence. `r.Next(i, myObject.Length);` is **ex** clusive so last element can be picked. – Andrey Jul 21 '11 at 14:48
  • @Andrey: Actually, I just noticed that you're using `r.Next(i, myObject.Length - i)`, which is completely broken. (I originally misread it as `-1`, not `-i`.) If you want an unbiased shuffle then you need `r.Next(i, myObject.Length)`. – LukeH Jul 21 '11 at 14:55
  • @LukeH oh, you are completely correct. I confused `.Next` with `string.Substring` where second param is count, not upper limit. – Andrey Jul 21 '11 at 14:57
1

using Random class of C# you can get random int which are less than a particular number which in your case will be the size of myObject

I am not sure you want unique or they can duplicate.

Haris Hasan
  • 29,856
  • 10
  • 92
  • 122
1

How about?

Random r = new Random();
int item1 = r.Next(0, myObject.Length);
int item2 = r.Next(0, myObject.Length);
int item3 = r.Next(0, myObject.Length);

var result1 = myObject[item1];
var result2 = myObject[item2];
var result3 = myObject[item3];

No Linq or anything, but it gets the job done.

CodingGorilla
  • 19,612
  • 4
  • 45
  • 65
  • This could be a problem. If item1 and item2 are the same? I'll extract only 2 items... – markzzz Jul 21 '11 at 13:43
  • Yea, it doesn't actually check to make sure you're not getting 3 copies of the same item (ie. you only have 1 item in the array). I guess I figured you could handle that. I was just trying to demonstrate the use of the `Random` class to get your random indexes. – CodingGorilla Jul 21 '11 at 13:45
  • Make a function that returns a random number, with the numbers you don't want passed in as an integer array? – Morten Bergfall Jul 21 '11 at 13:46
1

What about this:

 var random = new Random();
 var objs = new Object[] { 1, 2, 3, 4, 5, 6, 7, 8 };
 var result = objs.OrderBy(o => random.Next(Int32.MaxValue)).Take(3);
Yann Olaf
  • 597
  • 3
  • 12
1

A little bit more creative.

 var list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 1, 2, 3, 44, 5, 6 };
 Random random = new Random();
 var results = list.OrderBy(i => random.Next()).Take(3);

Output:

results: {int[3]}
[0]: 3
[1]: 2
[2]: 5
Four
  • 900
  • 1
  • 10
  • 18