8

I have got an IEnumerable collection as follows

var result1 = GetResult1() // Returns 2,4,5,6

I have to juggle the elements and create another collection in a random way which should result as follows:

var result2 = GetResult2(result1) // Returns 2,4,5,6 in a random order.
// An example output would be 4,6,2,5 in the resultant collection.

I have done this by the following means:

var result1 = GetResult1();
var random = new Random();
var result2 = result1.OrderBy(order=>random.Next());

However the issue with this is that if I access result2 the elements in result2 gets shuffled again, i.e if I output the results of result2 to a console twice, the elements are juggled again.

Can you please advice how to keep this uniform. i.e once I juggle the collection, it should remain the same way thereafter. I have to use lazy evaluation though, since the results are very huge in size.

Justin
  • 84,773
  • 49
  • 224
  • 367
Mike
  • 3,204
  • 8
  • 47
  • 74
  • Possible duplicate of [Randomize a List](https://stackoverflow.com/questions/273313/randomize-a-listt) – Liam Nov 28 '18 at 09:49

3 Answers3

6

You are looking for a shuffle algorithm.

See following SO threads to provide nice extensions method to do the job:

Is using Random and OrderBy a good shuffle algorithm?

An extension method on IEnumerable needed for shuffling


To answer your question "why is that shuffled again?", it's because of how OrderBy works (lazy execution). You could try:

var result2 = result1.OrderBy(order=>random.Next()).ToList();
Community
  • 1
  • 1
ken2k
  • 48,145
  • 10
  • 116
  • 176
3

I see you require lazy evaluation for the results, if that is the case, what you can do is this:

var randomNumbers = result1.Select(r => random.Next()).ToArray();
var orderedResult = result1.Zip(randomNumbers, (r, o) => new { Result = r, Order = o })
    .OrderBy(o => o.Order)
    .Select(o => o.Result);

By calling ToArray() on the random numbers, these will not change. When you finally desire the items in result1, you can zip the items with the random numbers, OrderBy the random number and Select the result.

As long as the items in result1 come in the same order, the result in orderedResult should be the same each time.

Lukazoid
  • 19,016
  • 3
  • 62
  • 85
  • This solution still doubles the original size of the result. Memory-wise, it is roughly equivalent to materializing the `IEnumerable`. – Marc Feb 20 '12 at 14:29
  • How is it? if each element of `result1` is a class which contains lots of fields, that is a lot of data. It will only be doubling if each element of `result1` contained one field the size of an int. The solution I have given here will consume memory storing the random numbers, but actually fetching the result data is deferred. – Lukazoid Feb 20 '12 at 15:08
  • who said they were objects? Per the OP, they appear to be integers. – Marc Feb 20 '12 at 17:05
  • Ah, good observation @Marc, if that is the case then you are right, this solution will practically double the memory usage. If `results1` is sourced from a server, this solution will still limit the hit and amount of data transfer going on. – Lukazoid Feb 20 '12 at 17:11
2

You can use ToList() like this:

var result2 = result1.OrderBy(order=>random.Next()).ToList();
soniiic
  • 2,664
  • 2
  • 26
  • 40
  • 1
    The asker requires lazy/deferred evalution as the items of `result1` are large in size. – Lukazoid Feb 20 '12 at 13:17
  • But at some point it must be enumerated, therefore the asker should `ToList()` it as late as possible, and then output the result set to whatever needs it – soniiic Feb 20 '12 at 14:09
  • What if the asker had some form of pagination? i.e. `Skip(20).Take(10)`, then the amount of data on the clients machine would be less, but if the `OrderBy` was being evaluated before each pagination call, the orders would come out differently. – Lukazoid Feb 20 '12 at 14:15
  • Well I can think of one implementation of that: a funny quotes website where quotes are random, but quotes are never shown more than once. – Lukazoid Feb 20 '12 at 15:11