9

I have an array of {0,1,2,3} and want to shuffle it. This is working pretty well

Public Function ShuffleArray(ByVal items() As Integer) As Integer()
    Dim ptr As Integer
    Dim alt As Integer
    Dim tmp As Integer
    Dim rnd As New Random()

    ptr = items.Length

    Do While ptr > 1
        ptr -= 1
        alt = rnd.Next(ptr - 1)
        tmp = items(alt)
        items(alt) = items(ptr)
        items(ptr) = tmp
    Loop
    Return items
End Function

some of the time. However, I'm finding that it often produces a stack of {1,2,3,0} where 0 is just placed on the back of the stack. In fact, often enough that this doesn't appear random at all. An "original sequence of 3 in the new randomized array" is not desired.

Is there anyway to improve this so that:

  1. An item is never in its original position
  2. A stack of 3 sequential items (from the original sequence is never allowed) (or any number of sequential original items)

It could be 6 items or 10 items in the array, but what I'm working with currently is just 4 items. C# or VB.net are fine.

Community
  • 1
  • 1
Todd Main
  • 28,951
  • 11
  • 82
  • 146
  • 2
    This sounds like an excellent problem for a functional language. – RLH Oct 04 '13 at 20:11
  • 1
    http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle – L.B Oct 04 '13 at 20:13
  • Well if your array size is really only 10 and no item may remain in the original position then there are only 9! permutations. You could brute force the problem and discard invalid sorts. Like this: http://stackoverflow.com/questions/11208446/generating-permutations-of-a-set-most-efficiently – Andrew Dec 08 '13 at 11:22
  • 1
    As others have pointed out, **there is a bug in your shuffle code**. See here: http://stackoverflow.com/questions/273313/randomize-a-listt-in-c-sharp Revisit the supposed desire for those two properties you list after you have a proper shuffling algorithm. – Timothy Shields Oct 23 '14 at 21:52
  • does "original sequence" and "original items" actually mean "previous series order"? For example: `{1, 3, 0, 2}` is a valid result; if you start with `{0, 1, 2, 3}` for the next iteration, `{1, 3, 0, 2}` is still valid even though it repeats everything. Also, just curious: is this for something like a clinical blind study? – Ňɏssa Pøngjǣrdenlarp Oct 28 '14 at 13:44
  • OP, you have not mentioned if duplicates are possible? If yes, how do they fit with constraints? – Erti-Chris Eelmaa Oct 28 '14 at 16:24
  • @ChrisEelmaa, in my case, no duplicates. – Todd Main Oct 28 '14 at 17:18
  • Check my answer below. I'm pretty sure it solves your problem, and if not, then tell me why. – Paul Ishak Oct 28 '14 at 23:39
  • There are two bugs in this code. The rnd.Next(ptr+1) bug is covered, the other bug is you creating an new instance of Random repeatedly. When you call the method twice in a row, you'll get the same sequence because Random will use the same seed. Move *rnd* outside of the method. – Hans Passant Oct 29 '14 at 21:02

11 Answers11

2

It looks like you're attempting a Fisher-Yates shuffle, but the call to Next is incorrect. The call to Next should be rnd.Next(ptr + 1). When you call it with ptr - 1 you only produce two permutations for a sequence of 4 items. With a starting sequence of [0 1 2 3] one of those sequences is [1 2 3 0]. See the table below for an explanation.

ptr  ptr - 1    alt     Permutations            Remarks
---  -------    ---     ------------            -------
4                       [0 1 2 3]               Starting condition
3    2          1 or 0  [0 3 2 1] or [3 1 2 0]  First pass 
2    1          0       [2 3 0 1] or [2 1 3 0]  Second pass
1    0          0       [3 2 0 1] or [1 2 3 0]  Final pass

The reason alt is 0 twice is that Random.Next(0) returns 0.

EDIT: Using rnd.Next(ptr), as noted by CoderDennis, instead of rnd.Next(ptr + 1) is probably closer to your requirement as it will do a better job moving digits to new positions. When you use rnd.Next(ptr + 1) you get more permutations, but with each possible loop you may not perform any swap which may leave a digit in its original position (depending on the location in the sequence and other swaps).

Mike Cowan
  • 919
  • 5
  • 11
2

I believe the following will meet the requirements given. I incorporated @CoderDennis's fix for the initial random value, and for passing in the Random. My VB skills have been tarnished by too many years in C# and JavaScript, so apologies for any obvious syntax errors.

It only filters out sequences of three sequential items, not "(or any number of sequential original items)".

Public Function ShuffleArray(ByVal items() As Integer, ByVal rnd As Random) As Integer()
    Dim original as Integer() = items.ToArray()
    Dim ptr As Integer
    Dim alt As Integer
    Dim tmp As Integer
    Dim stacksOfThree = new List(Of Integer())
    Dim isGood As Boolean = True

    ptr = items.Length

    Do While ptr > 2
        ptr -= 1
        stacksOfThree.Add(new Integer() { items(ptr - 2), items(ptr - 1), items(ptr) })
    Loop

    ptr = items.Length

    Do While ptr > 1
        ptr -= 1
        alt = rnd.Next(ptr)
        tmp = items(alt)
        While items(alt).Equals(items(ptr)) Or items(ptr).Equals(tmp)
            alt = rnd.Next(ptr)
            tmp = items(alt)
        End While
        items(alt) = items(ptr)
        items(ptr) = tmp
    Loop

    ptr = items.Length
    Do While ptr > 1
        ptr -= 1
        If items(ptr).Equals(original(ptr)) Then
            isGood = False
            Exit Do
        End If
    Loop

    If isGood Then
        ptr = items.Length
        Do While ptr > 2
            ptr -= 1
            For Each stack In stacksOfThree
                If stack(2).Equals(items(ptr)) And stack(1).Equals(items(ptr - 1)) And stack(0).Equals(items(ptr - 2)) Then
                    isGood = False
                    Exit For
                End If
            Next 
            If Not isGood Then
                Exit Do
            End If
        Loop
    End If

    If isGood Then
        Return items
    Else
        Return ShuffleArray(original, new Random())
    End If
End Function
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
2

Everyone's been addressing your shuffle and missing the actual issue.

With a constraint like this I would simply shuffle and then test if the result met the criteria, shuffling again if it did not. This unfortunately has an indeterminate runtime but so long as the constraint isn't too likely to reject it the real world performance is normally acceptable.

However, in this particular case I would take a different approach entirely. With 4 items in the list there are only 24 possible permutations, 4 of which are definitely invalid. (I'm not sure if you want things like [0, 1, 3, 2] or not.) Thus I would store all the valid permutations of the list, sort the list, pick a random permutation from a list of precalculated ones and "shuffle" the list accordingly.

Loren Pechtel
  • 8,945
  • 3
  • 33
  • 45
  • 1
    I've thought of storing valid permutations, which would seem fine for 3 or 4 number arrays. But if it reached 10 or 20 number arrays, that would a massive list to initialize. – Todd Main Oct 24 '14 at 15:18
  • @ToddMain That's why I was saying I would take that approach in this case--it's not a general solution to the problem. n=5 is as high as I would consider it without a very good reason. – Loren Pechtel Oct 24 '14 at 23:58
2

A stack of 3 sequential items (from the original sequence is never allowed)

I assume the result of shuffle(n) is what is used as the starting sequence for shuffle(n+1). This is non trivial because using the same start series results in only 7 valid combinations for {0, 1, 2, 3}. Using a fixed starting sequence when the app starts means the first shuffle can only be one of those 7 (probably varied enough).

A Scrambler class:

Public Class Scrambler
    Private rand As Random

    Public Sub New()
        rand = New Random
    End Sub

    ' FY In-Place integer array shuffle 
    Public Sub Shuffle(items() As Integer)
        Dim tmp As Integer
        Dim j As Integer

        ' hi to low, so the rand result is meaningful
        For i As Integer = items.Length - 1 To 0 Step -1
            j = rand.Next(0, i + 1)        ' NB max param is EXCLUSIVE

            tmp = items(j)
            ' swap j and i 
            items(j) = items(i)
            items(i) = tmp
        Next

    End Sub

    ' build a list of bad sequences

    ' fullfils the "stack of 3 sequential items (from the original sequence..." requirement
    ' nsize - allows for the "(or any number ..." portion though scanning for
    '   a series-of-5 may be fruitless
    Public Function GetBadList(source As Integer(),
                               nSize As Integer) As List(Of String)
        Dim BList As New List(Of String)
        Dim badNums(nSize - 1) As Integer

        For n As Integer = 0 To source.Length - nSize
            Array.Copy(source, n, badNums, 0, badNums.Length)
            BList.Add(String.Join(",", badNums))

            Array.Clear(badNums, 0, badNums.Length)
        Next
        Return BList
    End Function


    Public Function ScrambleArray(items() As Integer, badSize As Integer) As Integer()
        ' FY is an inplace shuffler, make a copy
        Dim newItems(items.Length - 1) As Integer
        Array.Copy(items, newItems, items.Length)

        ' flags
        Dim OrderOk As Boolean = True
        Dim AllDiffPositions As Boolean = True

        Dim BadList As List(Of String) = GetBadList(items, badSize)
        ' build the bad list

        Do
            Shuffle(newItems)

            ' check if they all moved
            AllDiffPositions = True
            For n As Integer = 0 To items.Length - 1
                If newItems(n) = items(n) Then
                    AllDiffPositions = False
                    Exit For
                End If
            Next

            ' check for forbidden sequences
            If AllDiffPositions Then
                Dim thisVersion As String = String.Join(",", newItems)

                OrderOk = True
                For Each s As String In BadList
                    If thisVersion.Contains(s) Then
                        OrderOk = False
                        Exit For
                    End If
                Next

            End If
        Loop Until (OrderOk) And (AllDiffPositions)

        Return newItems
    End Function

End Class

Test code/How to use it:

' this series is only used once in the test loop
Dim theseItems() As Integer = {0, 1, 2, 3}

Dim SeqMaker As New Scrambler         ' allows one RNG used
Dim newItems() As Integer

' reporting
Dim rpt As String = "{0}   Before: {1}   After: {2}  time:{3}"

ListBox1.Items.Clear()

For n As Integer = 0 To 1000
    sw.Restart()
    newItems = SeqMaker.ScrambleArray(theseItems, 3)  ' bad series size==3
    sw.Stop()

    ListBox1.Items.Add(String.Format(rpt, n.ToString("0000"), String.Join(",", theseItems),
                    String.Join(",", newItems), sw.ElapsedTicks.ToString))

    Console.WriteLine(rpt, n.ToString("0000"), String.Join(",", theseItems),
                      String.Join(",", newItems), sw.ElapsedTicks.ToString)

    ' rollover to use this result as next start
    Array.Copy(newItems, theseItems, newItems.Length)

Next

An item is never in its original position this sort of makes sense on small sets. But for larger sets, it rules out a large number of legitimate shuffles (>60%); in some cases just because 1 item is in the same spot.

 Start:   {1,2,8,4,5,7,6,3,9,0}
Result:   {4,8,2,0,7,1,6,9,5,3}

This fails because of the '6', but is it really an invalid shuffle? The series-of-three rule shows up pretty rarely in larger sets (<1%) that it might be a waste of time.


Without the listbox and console reports (and some distribution gathering not shown), it is pretty fast.

Std Shuffle, 10k iterations, 10 elements: 12ms  (baseline)
   Modified, 10k iterations, 10 elements: 91ms
   Modified, 10k iterations, 04 elements: 48ms

The modified shuffle relies on reshuffling which I knew would not be time consuming. So, when Rule1 OrElse Rule2 fails, it just reshuffles. The 10 element shuffle has to actually perform 28k shuffles to get 10,000 'good' ones. The 4 element shuffle actually has a higher rejection rate because the rules are easier to break with so few items (34,000 rejects).

That doesnt interest me nearly as much as the shuffle distribution, because if these "improvements" introduce a bias, it is no good. 10k 4 element distribution:

seq: 3,2,1,0  count: 425
seq: 1,0,2,3  count: 406
seq: 3,2,0,1  count: 449
seq: 2,3,1,0  count: 424
seq: 0,1,3,2  count: 394
seq: 3,0,2,1  count: 371
seq: 1,2,3,0  count: 411
seq: 0,3,1,2  count: 405
seq: 2,1,3,0  count: 388
seq: 0,3,2,1  count: 375
seq: 2,0,1,3  count: 420
seq: 2,1,0,3  count: 362
seq: 3,0,1,2  count: 396
seq: 1,2,0,3  count: 379
seq: 0,1,2,3  count: 463
seq: 1,3,0,2  count: 398
seq: 2,3,0,1  count: 443
seq: 1,0,3,2  count: 451
seq: 3,1,2,0  count: 421
seq: 2,0,3,1  count: 487
seq: 0,2,3,1  count: 394
seq: 3,1,0,2  count: 480
seq: 0,2,1,3  count: 444
seq: 1,3,2,0  count: 414

With smaller iterations (1K) you can see a more even distribution vs the modified form. But that is to be expected if you are rejecting certain legit shuffles.

Ten element distribution is inconclusive because there are so many possibilities (3.6 million shuffles). That said, with 10k iterations, there tends to be about 9980 series, with 12-18 having a count of 2.

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
1

I haven't implemented your rules about results never being in their original position or sequence of results. A truly random sequence will not conform to those rules anyway. However, I did find a couple issues with your implementation. I ran ShuffleArray in a loop 100 times in my tests. The code below seems to be much better at producing random results. Making sure that the instance of Random is created once before you loop through the calls to ShuffleArray eliminates the issues with the seed.

Also, your calls to Next were passing ptr - 1, which I don't think was correct. Just thinking about the first iteration of the loop, here is what your original code was doing:

  1. ptr = items.Length sets ptr to 4.
  2. ptr -= 1 sets ptr to 3.
  3. calling rnd.Next(ptr - 1) will pick an integer between 0 and 1.

Here's the updated code:

Public Function ShuffleArray(ByVal items() As Integer, ByVal rnd As Random) As Integer()
    Dim ptr As Integer
    Dim alt As Integer
    Dim tmp As Integer

    ptr = items.Length

    Do While ptr > 1
        ptr -= 1
        alt = rnd.Next(ptr)
        tmp = items(alt)
        items(alt) = items(ptr)
        items(ptr) = tmp
    Loop
    Return items
End Function

And here's an even simpler version using For instead of While:

Public Function ShuffleArray(ByVal items() As Integer, ByVal rnd As Random) As Integer()
    Dim alt As Integer
    Dim tmp As Integer

    For ptr = items.Length - 1 To 0 Step -1
        alt = rnd.Next(ptr)
        tmp = items(alt)
        items(alt) = items(ptr)
        items(ptr) = tmp
    Next
    Return items
End Function
CoderDennis
  • 13,642
  • 9
  • 69
  • 105
  • 2
    `Next` is never called with -1. In the last iteration of the loop, `ptr` starts as 2, is decremented to 1, and `Next` is called with `ptr - 1` or 0, which returns 0. – Mike Cowan Oct 23 '14 at 21:41
  • 1
    Your third option is not correct, because `Random.Next` returns a value less than its argument. So `rnd.Next(ptr - 1)` will pick an integer between `0` and `1`. – Dmitry Oct 23 '14 at 21:42
  • @Dmitry you are also correct. I've edited the answer to reflect that. – CoderDennis Oct 23 '14 at 21:53
1

A simple solution for small sets:

public static List<int> Shuffle(List<int> ints)
{
    var random = new Random();
    var result = ints.ToList();
    var hs = new HashSet<int>(ints);
    for (int i = 0; i < ints.Count; i++)
    {
        result[i] = ints.Where((x, j) => j != i).Intersect(hs).OrderBy(x => random.Next()).First();
        hs.Remove(result[i]);
    }
    return result;
}
brz
  • 5,926
  • 1
  • 18
  • 18
  • This looks fascinating? Besides just randomization, does it do one or both the request above (*1)* nothing in it's original position and *2)* no sequence of 3s)? – Todd Main Oct 24 '14 at 15:16
  • The `Where((x, j) => j != i)` part insures that there's no element in it's original position. It doesn't guarantee that there's no sequence of 3s in large sets, but in small sets it's highly unlikely. – brz Oct 24 '14 at 15:23
1

Here i suggest a method through which you can achieve your target of shuffling, by taking one number as random and shift others to fill the array. consider the following code:

Public Function ShuffleArray(ByVal a() As Integer) As Integer()
    Dim ptr As Integer
    Dim alt As Integer
    Dim items(3) As Integer
    Dim rnd As New Random()
    ptr = a.Length
    alt = rnd.Next(1, 4) '<---- change here it now generate a random number between 1 and 3
    Do While ptr <> 0
        ptr -= 1
        items(ptr) = a(alt)
        Select Case alt
            Case 0 To 2
                alt += 1
            Case Else
                alt = 0
        End Select
    Loop
    Return items
End Function 

The items will contains shuffled arrays, Example:

1 2 3 0
2 3 0 1
3 0 1 2 etc

Updates:

My answer will not produce the output 0,1,2,3 since alt = rnd.Next(1, 4) will produce a number between 1(lowerLimit) and 4(upperLimit). the Link says that rnd.Next(lowerLimit,upperLimit) will produce a random number including lowerLimit and Excluding the upperLimit.

Based on this random number generated the upcoming sequences are generated, since it will not produce 0 the sequence 0,1,2,3 will not be generated.

Hope that the answer is clear enough

CoderDennis
  • 13,642
  • 9
  • 69
  • 105
  • 2
    if it's producing "1 2 3 0" as in your example, then this doesn't meet the request. – Todd Main Oct 24 '14 at 15:17
  • @ Todd Main : no it will not produce`0,1,2,3` reason is updated in the answer –  Oct 25 '14 at 03:30
  • This is fine for applying the series rule but it has a serious problem. If you feed the result back into it for the next iteration, you get the previous series back. The result is that it can only create 2 of the possible 24 combinations. – Ňɏssa Pøngjǣrdenlarp Oct 29 '14 at 02:27
1

It's much easier if you reduce the problem to shuffling an array of indices, and then use this array to order an array of item. I created a GuardedShuffle class, which demonstrates how this can be done. It currently defines a sequential element as one, for which nearby value was mentioned in the original sequence, ascending or descending.

The code is human's interpretation of your shuffling rules, i.e. how I would solve it by hand, written in VB.NET. I didn't not try to optimize it, but it should be fast enough for reasonably sized collections.

If you have any questions on the code - let me know in comments, I'll try my best to explain - although I tried to keep it clean, without going too crazy with refactoring and coding standards.

You may want to add some error checking, before including this as part of a real application.

Module Module1

  Sub Main()
    Dim items() As Integer = {0, 11, 22, 33, 44, 55, 66, 77, 88, 99}
    Dim gs As New GuardedShuffle(maxSequentialItems:=1)
    Dim shuffledItems() As Integer = gs.ShuffleArray(items)
  End Sub

  Public Class GuardedShuffle

    Private _maxSequentialItems As Integer

    Public Sub New(maxSequentialItems As Integer)
      _maxSequentialItems = maxSequentialItems
    End Sub

    Public Function ShuffleArray(items() As Integer) As Integer()
      Dim indicesSequential As New List(Of Integer)
      For i = 0 To items.Count - 1
        indicesSequential.Add(i)
      Next

      Dim indicesShuffled() As Integer = ShuffleIndices(indicesSequential.ToArray)

      Dim retValue As New List(Of Integer)
      For i = 0 To items.Count - 1
        retValue.Add(items(indicesShuffled(i)))
      Next

      Return retValue.ToArray
    End Function

    Private Function ShuffleIndices(indices() As Integer) As Integer()
      Dim inputList As New List(Of Integer)(indices)
      Dim outputList As New List(Of Integer)

      Dim r As New Random
      While inputList.Count > 0
        Dim seq As New List(Of Integer)
        If _maxSequentialItems = 1 AndAlso outputList.Count > 0 Then
          seq.Add(outputList.Last)
        Else
          For k As Integer = outputList.Count - _maxSequentialItems + 1 To outputList.Count - 1
            If k >= 0 Then
              seq.Add(outputList(k))
            End If
          Next
        End If

        Dim allowedList As New List(Of Integer)
        For Each el In inputList
          If IsAllowed(seq, el, _maxSequentialItems) Then
            allowedList.Add(el)
          End If
        Next
        allowedList.Remove(outputList.Count) 'An item is never in its original position

        Dim randomIndex As Integer = Math.Floor(r.Next(allowedList.Count))
        Dim i As Integer = allowedList.Item(randomIndex)
        inputList.Remove(i)
        outputList.Add(i)
      End While

      Return outputList.ToArray
    End Function

    Private Shared Function IsAllowed(curSeq As List(Of Integer), newValue As Integer, maxSequential As Integer) As Boolean
      Dim seq As New List(Of Integer)(curSeq)
      seq.Add(newValue)
      Return IsAllowed(seq, maxSequential)
    End Function

    Private Shared Function IsAllowed(seq As List(Of Integer), maxSequential As Integer) As Boolean
      Dim curSequential As Integer = 0
      For i = 1 To seq.Count - 1
        If Math.Abs(seq(i) - seq(i - 1)) = 1 Then
          curSequential += 1
        End If
      Next
      Return curSequential < maxSequential
    End Function

  End Class

End Module

Performance/scalability testing (shuffles 1000 items in less than 100ms):

Sub Main()
  Dim items() As Integer = Enumerable.Range(0, 1000).ToArray
  Dim gs As New GuardedShuffle(maxSequentialItems:=1)
  Dim t As New Stopwatch
  t.Start()
  Dim shuffledItems() As Integer = gs.ShuffleArray(items)
  t.Stop()
  Console.WriteLine("Elapsed (ms): " & t.ElapsedMilliseconds.ToString("N2"))
  Console.ReadLine()
End Sub

Using the same code, 10000 items are sorted in 7-8 seconds.

Victor Zakharov
  • 25,801
  • 18
  • 85
  • 151
  • 1
    Wow, this is brilliant! I'll run some tests on it. – Todd Main Oct 27 '14 at 22:33
  • @ToddMain: Thanks, I just thought of an edge case for it. I'm getting `Index was out of range. Must be non-negative and less than the size of the collection.` at `Dim i As Integer` line when an array has two elements and max sequential items = 1. This is because neither of the two permutations fulfills your requirement, {0, 1} and {1, 0} are both sequential, and the first one also has all items at their original positions. You may want to wrap this line in a Try...Catch block and throw some known exception type, for example InvalidOperationException . – Victor Zakharov Oct 27 '14 at 23:47
1

have you considered something like this? This will not show any sequential string of integers longer than 2. This will not place any item in it's original position either.

Note** If you make identical elements(such as having a 9 appear in 2 different places within the array), then it would be possible to shuffle 9a into the slot of 9b and likewise, because the shuffle function does not consider the value of what it is shuffling, it only shuffles by the array indexes.

Option Strict On
Option Explicit On
Option Infer Off
Public Class Form1
    Dim rnd As New Random(Today.Millisecond)
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim originalArray As Integer() = {0, 1, 2, 3, 4, 5, 6, 7, 9}
        Dim sb As New System.Text.StringBuilder
        Dim final As New System.Text.StringBuilder
        Dim msg As String = "Original: {0} Shuffled: {1}"
        Dim msg2 As String = "Original: {0} Shuffled: {1} ◄- Same"
        For i2 As Integer = 1 To 3
            Dim shuffledArray As Integer() = ShuffleArray(originalArray)
            For i As Integer = 0 To shuffledArray.Count - 1
                If originalArray(i) = shuffledArray(i) Then
                    sb.AppendLine(String.Format(msg2, originalArray(i), shuffledArray(i)))
                Else
                    sb.AppendLine(String.Format(msg, originalArray(i), shuffledArray(i)))
                End If
            Next
            Dim result As String = sb.ToString.Substring(0, sb.ToString.Length - 1) & vbCrLf & vbCrLf
            final.AppendLine(result)
            sb.Clear()
        Next
        RichTextBox1.Text = final.ToString
    End Sub
    Public Function ShuffleArray(Of t)(ByVal items() As t) As t()
        Dim results As New List(Of t)
        Dim usedIndexes As New List(Of Integer)
        Do
            Dim nextIndex As Integer = rnd.Next(0, items.Count)
            If usedIndexes.IndexOf(nextIndex) = -1 Then
                If usedIndexes.Count = nextIndex Then
                    If usedIndexes.Count = items.Count - 1 Then
                        usedIndexes.Clear()
                        results.Clear()
                    End If
                    Continue Do
                End If
                If Not last3sequential(usedIndexes, nextIndex) Then
                    usedIndexes.Add(nextIndex)
                    results.Add(items(nextIndex))
                Else
                    If usedIndexes.Count > items.Count - 3 Then
                        usedIndexes.Clear()
                        results.Clear()
                    End If
                End If
            End If
        Loop Until results.Count = items.Count
        Return results.ToArray
    End Function
    Function last3sequential(usedIndexes As List(Of Integer), nextIndex As Integer) As Boolean
        If usedIndexes.Count < 2 Then Return False
        Dim last As Integer = nextIndex
        Dim secondToLast As Integer = usedIndexes(usedIndexes.Count - 1)
        Dim thirdToLast As Integer = usedIndexes(usedIndexes.Count - 2)
        If last - secondToLast = 1 AndAlso secondToLast - thirdToLast = 1 Then
            Return True
        End If
        Return False
    End Function
End Class

5 test cases:
Original: 0 Shuffled: 7
Original: 1 Shuffled: 8
Original: 2 Shuffled: 5
Original: 3 Shuffled: 2
Original: 4 Shuffled: 9
Original: 5 Shuffled: 4
Original: 6 Shuffled: 0
Original: 7 Shuffled: 6
Original: 8 Shuffled: 3
Original: 9 Shuffled: 1 

Original: 0 Shuffled: 4
Original: 1 Shuffled: 2
Original: 2 Shuffled: 9
Original: 3 Shuffled: 6
Original: 4 Shuffled: 7
Original: 5 Shuffled: 0
Original: 6 Shuffled: 3
Original: 7 Shuffled: 5
Original: 8 Shuffled: 1
Original: 9 Shuffled: 8 

Original: 0 Shuffled: 8
Original: 1 Shuffled: 7
Original: 2 Shuffled: 6
Original: 3 Shuffled: 2
Original: 4 Shuffled: 0
Original: 5 Shuffled: 1
Original: 6 Shuffled: 9
Original: 7 Shuffled: 4
Original: 8 Shuffled: 5
Original: 9 Shuffled: 3 

Original: 0 Shuffled: 6
Original: 1 Shuffled: 4
Original: 2 Shuffled: 8
Original: 3 Shuffled: 7
Original: 4 Shuffled: 9
Original: 5 Shuffled: 2
Original: 6 Shuffled: 5
Original: 7 Shuffled: 3
Original: 8 Shuffled: 1
Original: 9 Shuffled: 0 

Original: 0 Shuffled: 6
Original: 1 Shuffled: 9
Original: 2 Shuffled: 0
Original: 3 Shuffled: 1
Original: 4 Shuffled: 5
Original: 5 Shuffled: 2
Original: 6 Shuffled: 3
Original: 7 Shuffled: 8
Original: 8 Shuffled: 7
Original: 9 Shuffled: 4 
Paul Ishak
  • 1,093
  • 14
  • 19
1

If you put a criteria like yours, the shuffle lose its random characteristic. And here is some implementation of Knuth shuffle algorithm with test program. the two main think I am doing is to ensure that Random is a global variable and to pick an element from i and N, knowing that i is the index of the loop and N is the size of the array.

using System;
using System.Collections.Generic;
using System.Linq;

public class Solution
{
  private static void Main(String[] args)
  {
    var array = new int[] { 1, 2, 3, 4 };
    Dictionary<string, int> results = new Dictionary<string, int>();
    for (int i = 0; i < 500000; i++)
    {
      var a = array.ToArray();
      Shuffller.Shuffle(a);
      var data = string.Join(" ", a);
      if (results.ContainsKey(data))
      {
        results[data]++;
      }
      else
      {
        results.Add(data, 1);
      }
    }

    foreach (var item in results.OrderBy(e => e.Key))
    {
      Console.WriteLine("{0}  => {1}", item.Key, item.Value);
    }
    Console.ReadKey();
  }

  public class Shuffller
  {
    private static Random random = new Random();
    /// <summary>
    /// * Rearranges an array of objects in uniformly random order
    ///  (under the assumption that Random generates independent
    ///  and uniformly distributed numbers).          
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="a">the array to be shuffled     </param>
    public static void Shuffle<T>(T[] a)
    {
      int N = a.Length;
      for (int i = 0; i < N; i++)
      {
        // choose index uniformly in [i, N-1]        
        int r = i + random.Next(0, N - i);
        T swap = a[r];
        a[r] = a[i];
        a[i] = swap;
      }
    }


  }  
  }

If you want to element some result, why not implement a similarity algorithm between two arrays and then define a threshold, if the similarity between the shuffled array and the original is above the threshold then re-shuffle, although I don't recommend touching the result especially if you need random shuffling, you will have many flaws if your algorithm is based on random shuffling.

Swift
  • 1,861
  • 14
  • 17
-1

I believe what you are seeing is because you aren't seeding your Random class with a value. I would recommend trying the constructor that accepts an int as the seed value. A simple seed value would be the number of Ticks in DateTime.Now.Ticks

For more info on the Random class see the following link: http://msdn.microsoft.com/en-us/library/system.random.aspx

Scope Creep
  • 547
  • 3
  • 15
  • 1
    Read the link you supplied as reference - Random uses `DateTime.Now.Ticks` as it's seed by default. – McAden Oct 04 '13 at 20:27
  • Maybe I'm reading it wrong but I don't think that's what it says. The article reads: "By default, the parameterless constructor of the Random class uses the system clock to generate its seed value, while its parameterized constructor can take an Int32 value based on the number of ticks in the current time." So yes, the parameterless constructor does use the clock, it just doesn't specify how. It continues: "...using the parameterless constructor to create different Random objects in close succession creates random number generators that produce identical sequences of random numbers." – Scope Creep Oct 04 '13 at 20:36
  • I've had `New Random` at both the procedure level and the module level and the result is the same. At the modular level, it should ensure randomness according to all sources - but that isn't really the problem. The problem is that it shuffles the array with the result being `{1,2,3,0}` far too often (I actually don't want that at all - or `{2,3,0,1}` or `{3,0,1,2}` for that matter). – Todd Main Oct 04 '13 at 20:38
  • 1
    @ScopeCreep - That's because `DateTime.Now.Ticks` can be the same "in close proximity" as stated. I was incorrect in that it doesn't use `DateTime.Now.Ticks` and in fact uses Environment.Ticks. [Code Here](http://ideone.com/OQFhB7). It doesn't, however, change the fact that seed isn't an issue here. – McAden Oct 04 '13 at 20:57
  • 1
    @McAden - Touche good sir...touche... Alright, well then perhaps the code actually is generating random sequences and the issue is that with 4 items there are only 24 different ways it can be "shuffled" and at least a few of those are undesirable? – Scope Creep Oct 04 '13 at 21:23
  • @ScopeCreep, I'm finding that it produces the `{1,2,3,0}` sequence about once every 3-4 times. Using the code above, I'm assuming that it doesn't allow for an item to be in its original position. With that, there are only 8 valid shuffles. To meet my requirement above of a) no original position and b) no sequence of 3 from original array, there would only be 5 valid shuffles. – Todd Main Oct 04 '13 at 21:30
  • Oh, just now saw that you mentioned in a comment above that you've already had `New Random` at the module level. The `Next(ptr - 1)` call was probably more at fault, but a single instance of `Random` at the module level is best. – CoderDennis Oct 23 '14 at 21:04