views:

1268

answers:

8

I have a list of type System.IO.FileInfo, and I would like to randomize the list. I thought I remember seeing something like list.randomize() a little while back but I cannot find where I may have seen that.

My first foray into this yielded me with this function:

Private Shared Sub GetRandom(ByVal oMax As Integer, ByRef currentVals As List(Of Integer))
    Dim oRand As New Random(Now.Millisecond)
    Dim oTemp As Integer = -1
    Do Until currentVals.Count = IMG_COUNT
        oTemp = oRand.Next(1, oMax)
        If Not currentVals.Contains(oTemp) Then currentVals.Add(oTemp)
    Loop
End Sub

I send it the max val I want it to iterate up to, and a reference to the list I want the randomized content in. The variable IMG_COUNT is set farther up in the script, designating how many random images I want displayed.

Thanks guys, I appreciate it :D

A: 

You could create custom comparer that just returns a random number, then sort the list using this comparer. It could be horribly inefficient and cause an almost infinite loop, but might be worth a try.

ck
in other words, is my method a good way to go?
Anders
No, you definitely want to use array.Sort() with some custom comparator. It's just a matter of how to implement the comparator-> possibly based on each object's GetHashCode() value.
Joel Coehoorn
is there any good resources that you know of for custom comparators? i havent looked much into those as of late *ducks*
Anders
Actually, that was a typo: I meant Comparer. Example is forthcoming.
Joel Coehoorn
-1: This is another bad implementation. .NET will throw an "Array.Sort failed to compare to values" exception if two elements return inconsistent sorts. For example, if A > B, and B > C, but a comparison of A and C together indicates that C > A, then we have a problem.
Juliet
+10  A: 

Check out the Fisher-Yates shuffle algorithm here: http://en.wikipedia.org/wiki/Knuth_shuffle

with a more concise discussion by this site's chief overlord here: http://www.codinghorror.com/blog/archives/001015.html

There is a simple C# implementation in the blog entry that should be real easy to change to VB.NET

nick
great link I am going to check this out for sure!
Anders
+1  A: 

You could also implement a shuffle, many ways to do this, the simplest is randomly pick a item and insert it into a new location a bunch of times.

pezi_pink_squirrel
A: 

Something like this could work:

Dictionary<int, int> randomCollection = new Dictionary<int, int>(currentVals.Count);
foreach( int currentValue in currentVals ) {
  randomCollection.Add( Random.Next(1,999), currentValue );
}

return randomCollection.Values;

Obviously you'd have to account for collisions with the "stupid" random key..

Just a thought..

Ryan Emerle
-1: don't encourage the OP to use ugly hacks like this. There are well-known algorithms for solving the users problem.
Juliet
A: 

If you have the number of elements then a pseudo-random method can be used whereby you choose the first element at random (e.g. using the inbuilt random number function) then add a prime and take the remainder after division by the number of values. e.g. for a list of 10 you could do i = (i + prime) % 10 to generated indices i from some starting value. As long as the prime is greater than the number of values in the list then you create a sequence which runs through all of the numbers 0...n where n is the number of values - 1, but in a pseudorandom order.

jheriko
+1  A: 

Build a Comparer:

Public Class Randomizer(Of T)
    Implements IComparer(Of T)

    ''// Ensures different instances are sorted in different orders
    Private Shared Salter As New Random() ''// only as random as your seed
    Private Salt As Integer
    Public Sub New()
        Salt = Salter.Next(Integer.MinValue, Integer.MaxValue)
    End Sub

    Private Shared sha As New SHA1CryptoServiceProvider()
    Private Function HashNSalt(ByVal x As Integer) As Integer
      Dim b() As Byte = sha.ComputeHash(BitConverter.GetBytes(x))
      Dim r As Integer = 0
      For i As Integer = 0 To b.Length - 1 Step 4
          r = r Xor BitConverter.ToInt32(b, i)
      Next

      Return r Xor Salt
    End Function

    Public Function Compare(x As T, y As T) As Integer _
        Implements IComparer(Of T).Compare

        Return HashNSalt(x.GetHashCode()).CompareTo(HashNSalt(y.GetHashCode()))
    End Function
End Class

Use it like this, assuming you mean a generic List(Of FileInfo):

list.Sort(New Randomizer(Of IO.FileInfo)())

You can also use a closure to make the random value 'sticky' and then just use linq's .OrderBy() on that (C# this time, because the VB lambda syntax is ugly):

list = list.OrderBy(a => Guid.NewGuid()).ToList();

Explained here, along with why it might not even be as fast as real shuffle:
http://www.codinghorror.com/blog/archives/001008.html?r=31644

Joel Coehoorn
I keep getting an error: "Class 'Randomizer' must implement 'Function Compare(x as T,y as T) As Integer' for interface 'System.Collections.Generic.IComparer(of T)'." This error is gotten just using your second block of code.
Anders
Note that with the 2nd option that method doesn't need to live in a separate class, and you use it via the AddressOf operator as shown, rather than by creating a class instance.
Joel Coehoorn
-1: Just a bad implementation. The function doesn't actually randomize anything, because two lists containing the same items will be "randomized" in the same order. Also, nothing prevents sequential items from having sequential hashcodes. There are much better ways to write this function.
Juliet
ahah perfect, i missed that part :P thanks again!
Anders
Note that Princess brings up a very valid complaint: the same list will always be sorted the same way. The sequential hashcode part is less valid, because I only recommend .GetHashCode() as a starting point, but the first complaint is still pretty damning.
Joel Coehoorn
However, I am still convinced this can work as a base implementation: you would just need to add an additional (private) member to the Randomizer class that can be included in the calculated result so that different instances of the class will yield different results.
Joel Coehoorn
just so you guys know, it works better than the original function that i posted. this particular implementation doesnt need to have a super awesome randomize to it, as all it does is get a set of images, randomizes their order, and makes them into one image (dynamic banner so to speak)
Anders
since this is my first real plunge into IComparers, ill be experimenting with it to see what all i can do with it.
Anders
Just beware: as posted, this code will always create the same banner from the same images. You need to add an additional step to make it really random.
Joel Coehoorn
@Joel: I consider sequential hashcodes a *very* bad thing, because it doesn't put items in random order. To give two examples, a list of ints 1..10 have the hashcodes 1, 2, 3, 4 .. 10. A list of strings "A", "B", "C", .. "Z" have hashcodes -842352673, -842352674, -842352675, -842352676, -842352677
Juliet
I agree, which is why _in the text explaining the code_ I suggest also passing the HashCodes through a different hashing mechanism.
Joel Coehoorn
Updated the class version to be at least somewhat random.
Joel Coehoorn
I had to play with it some more even though Fisher-Yates is in all senses better, just because I can see the use in wanting to have be able to swap in one of a number of different sorting options, including random, based on a user choice at runtime.
Joel Coehoorn
thanks joel for your considerable contributions :D. Being newish at comparers, I am still getting an error when using the class-based code that I posted up earlier. The function is there, what else do I need to do?
Anders
For this latest version you also need the system.security.cryptography namespace imported, and you are back to using a new instance with your sort rather the address of a function
Joel Coehoorn
Also, there was a type which I just fixed a moment ago- just one extra letter.
Joel Coehoorn
Finally, my code was working in my test project, but when I loaded it again this morning it was complaining at me about implementing the compare function, so I added some code to make it explicitly implement the interface.
Joel Coehoorn
I had to change the Return line for the Compare function: Return HashNSalt(x.GetHashCode()).CompareTo(HashNSalt(y.GetHashCode())). I am not sure if the .Compare() is a C# function, but it threw an error stating .Compare wasnt a member of integer. Works great! thanks again
Anders
+1  A: 

There are several reasonable methods of shuffling.

One has already been mentioned. (The Knuth Shuffle.)

Another method would be to assign a "weight" to each element and sort the list according to that "weight." This method is possible but would be unweildy because you cannot inherit from FileInfo.

One final method would be to randomly select an element in the original list and add it to a new list. Of course, that is, if you don't mind creating a new list. (Haven't tested this code...)


        Dim rnd As New Random
        Dim lstOriginal As New List(Of FileInfo)
        Dim lstNew As New List(Of FileInfo)

        While lstOriginal.Count > 0
            Dim idx As Integer = rnd.Next(0, lstOriginal.Count - 1)
            lstNew.Add(lstOriginal(idx))
            lstOriginal.RemoveAt(idx)
        End While
cool, ill keep this in mind for the future.
Anders
A: 
Dim oRand As New Random() 'do not seed!!!!
Private Sub GetRandom(ByRef currentVals As List(Of Integer))
    Dim i As New List(Of Integer), j As Integer
    For x As Integer = 0 To currentVals.Count - 1
        j = oRand.Next(0, currentVals.Count)
        i.Add(currentVals(j))
        currentVals.RemoveAt(j)
    Next
    currentVals = i
End Sub
dbasnett