(.NET 3.5) Randomise a List of Items
C# version here.
The following is an extension method for getting the contents of any IEnumerable(Of T) object, e.g. an array or List(Of T), in random order:
Code:
Imports System.Runtime.CompilerServices
Public Module Enumerable
<Extension()> _
Public Function Randomise(Of T)(ByVal items As IEnumerable(Of T)) As IEnumerable(Of T)
Dim result As T() = Nothing
If items IsNot Nothing Then
Dim rng As New Random
result = (From item In items Order By rng.NextDouble()).ToArray()
End If
Return result
End Function
End Module
Note that it obeys the rules for extension methods, i.e. it's a member of a module, is decorated with the Extension attribute and takes the target type as the first argument. You might use it something like this:
vb.net Code:
Dim numbers As Integer() = {1, 2, 3, 4, 5, 6, 7, 8, 9}
For Each number In numbers.Randomise()
MessageBox.Show(number.ToString())
Next
Try running that repeatedly and you'll get a different order each time.
Note that you could write a regular method that does the same thing in earlier versions.
EDIT: It's been brought to my attention that there is a significant issue with this code that, while unlikely to be encountered, could cause the operation to fail. Here's a reimplementation that isn't susceptible to this issue:
vb.net Code:
Imports System.Runtime.CompilerServices
Public Module Enumerable
<Extension>
Public Function Randomise(Of T)(ByVal items As IEnumerable(Of T)) As IEnumerable(Of T)
Dim result As T() = Nothing
If items IsNot Nothing Then
Dim rng As New Random
result = items.ToArray()
'Generate one random value per item.
Dim keys = Array.ConvertAll(result, Function(item) rng.NextDouble())
'Sort the items by their corresponding random keys, thus randomising them.
Array.Sort(keys, result)
End If
Return result
End Function
End Module
Re: (.NET 3.5) Randomise a List of Items
Great code John! (Btw, I think it's randomiZe, I have a thing for spelling, if not, blame Google :P).
Re: (.NET 3.5) Randomise a List of Items
Quote:
Originally Posted by
tassa
Great code John! (Btw, I think it's randomiZe, I have a thing for spelling, if not, blame Google :P).
It's "randomize" if you speak American, "randomise" if you speak English. ;)
Re: (.NET 3.5) Randomise a List of Items
Re: (.NET 3.5) Randomise a List of Items
Wow, it's got LINQ, Generic types and an extension method; that's some pretty powerful coding there! Kudos!
Re: (.NET 3.5) Randomise a List of Items
Note that I have simplified the code a little from the original. The new code will not throw an exception if you call it on a null reference but it will return a null reference. As such, an exception will be thrown if you try to enumerate the result, which did not happen with the old code. You could easily adjust the code to return an empty array if a null reference is passed in to avoid that if you think it's appropriate.
Re: (.NET 3.5) Randomise a List of Items
I already made that update when I copied it into my extensibility utility module last night. :) I like my methods to return Nothing in most null reference errors and thus, can check for that condition prior to enumeration.
Re: (.NET 3.5) Randomise a List of Items
One small issue with the above code. You're creating a new instance of the Random class in the extension method itself. Thus if you call it multiple times in short succession, each execution will get a Random instance initialised with the same seed. Instead, it is better to create a single Random instance for your entire app and pass that reference around, like this:
Code:
<Extension()> _
Public Function Randomise(Of T)(ByVal items As IEnumerable(Of T), ByRef random As System.Random) As IEnumerable(Of T)
Dim rng = random
Dim result As T() = Nothing
If items IsNot Nothing Then
result = (From item In items Order By rng.NextDouble()).ToArray()
End If
Return result
End Function
The following calling code demonstrates the issue with the original method:
Code:
Sub Main()
Dim numbers As Integer() = {1, 2, 3, 4, 5, 6, 7, 8, 9}
For i = 1 To 2
Console.Write("Run {0}: ", i)
For Each number In numbers.Randomise()
Console.Write("{0} ", number)
Next
Console.WriteLine()
Next
Console.WriteLine()
Dim rng As New System.Random
For i = 1 To 2
Console.Write("Run {0}: ", i)
For Each number In numbers.Randomise(rng)
Console.Write("{0} ", number)
Next
Console.WriteLine()
Next
Console.ReadLine()
End Sub
Gives the following output:
Code:
Run 1: 2 5 3 6 4 1 8 7 9
Run 2: 2 5 3 6 4 1 8 7 9
Run 1: 2 5 3 6 4 1 8 7 9
Run 2: 5 6 7 9 2 8 1 3 4
Re: (.NET 3.5) Randomise a List of Items
Quote:
Originally Posted by
Evil_Giraffe
One small issue with the above code. You're creating a new instance of the Random class in the extension method itself. Thus if you call it multiple times in short succession, each execution will get a Random instance initialised with the same seed.
I'm well aware of that potential issue with the Random class. In this case though, what are the chances that you will have multiple lists that you want to randomise in such quick succession that each Random object will use the same seed?
The idea of using a single Random object for an entire application and passing it around is not really practical in many cases. This extension method would be basically pointless if you had to create your own Random object to use it. In that case you may as well just use your own LINQ query.
Re: (.NET 3.5) Randomise a List of Items
You could just put the Random object as a Private field inside the Module:
vb.net Code:
Imports System.Runtime.CompilerServices
Public Module Enumerable
'//fields
Private randomInstance As Random
'//methods
<Extension()> _
Public Function Randomize(Of T)(ByVal source As IEnumerable(Of T)) As IEnumerable(Of T)
If source IsNot Nothing Then
Dim r As Random
If Enumerable.randomInstance Is Nothing Then
Enumerable.randomInstance = New Random()
End If
r = Enumerable.randomInstance
Return source.OrderBy(Function() r.NextDouble()).ToArray()
End If
Return Nothing
End Function
End Module
Re: (.NET 3.5) Randomise a List of Items
Good point, but that'd be cleaner with a static initialiser:
vbnet Code:
'//fields
Private randomInstance As System.Random = New System.Random
'//methods
<Extension()> _
Public Function Randomize(Of T)(ByVal source As IEnumerable(Of T)) As IEnumerable(Of T)
If source IsNot Nothing Then
Return source.OrderBy(Function() randomInstance.NextDouble()).ToArray()
End If
Return Nothing
End Function
Re: (.NET 3.5) Randomise a List of Items
Except if you don't use the function you just created a object that doesn't ever get used.