【问题标题】:ensuring a sequential stack of 3 doesn't appear in a shuffled array of 4?确保 3 的顺序堆栈不会出现在 4 的洗牌数组中?
【发布时间】:2013-10-11 23:04:18
【问题描述】:

我有一个 {0,1,2,3} 数组,并且想要对其进行随机播放。 This 运行良好

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

有时。但是,我发现它经常会产生一堆{1,2,3,0},其中0 只是放在堆栈的后面。事实上,这通常足以使这看起来根本不是随机的。不需要“新随机数组中的 3 的原始序列”。

有没有办法改进这个:

  1. 项目永远不会位于其原始位置
  2. 3 个连续项目的堆栈(从原始序列中永远不会 允许)(或任意数量的连续原始项目)

数组中可能有 6 项或 10 项,但我目前正在使用的只有 4 项。 C# 或 VB.net 都可以。

【问题讨论】:

  • 这对于函数式语言来说听起来是一个很好的问题。
  • 好吧,如果您的数组大小真的只有 10 并且没有项目可能保留在原始位置,那么只有 9!排列。您可以暴力破解问题并丢弃无效排序。像这样:stackoverflow.com/questions/11208446/…
  • 正如其他人所指出的,您的随机播放代码中有一个错误。请参阅此处:stackoverflow.com/questions/273313/randomize-a-listt-in-c-sharp 在您拥有适当的洗牌算法后,重新审视您列出的这两个属性的假设需求。
  • “原始序列”和“原始项目”实际上是指“以前的系列订单”吗?例如:{1, 3, 0, 2} 是有效结果;如果您从{0, 1, 2, 3} 开始下一次迭代,{1, 3, 0, 2} 仍然有效,即使它重复所有内容。另外,只是好奇:这是用于临床盲研究吗?

标签: c# .net arrays vb.net shuffle


【解决方案1】:

一叠 3 个连续的项目(从原始序列中是不允许的)

我假设 shuffle(n) 的结果被用作 shuffle(n+1) 的起始序列。这不是微不足道的,因为使用相同的开始系列只会导致 {0, 1, 2, 3} 的 7 个有效组合。在应用启动时使用固定的启动顺序意味着第一次 shuffle 只能是这 7 个中的一个(可能足够多)。

一个 Scrambler 类:

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

测试代码/使用方法:

' 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

一个项目永远不会在它的原始位置这种在小集合上是有意义的。但对于较大的集合,它排除了大量的合法洗牌(>60%);在某些情况下,仅仅因为 1 个项目在同一个位置。

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

由于“6”而失败,但它真的是无效的随机播放吗?三个系列规则很少出现在较大的集合中 (


没有列表框和控制台报告(以及一些未显示的分发收集),它非常快。

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

修改后的洗牌依赖于重新洗牌,我知道这不会耗费时间。因此,当 Rule1 OrElse Rule2 失败时,它只是重新洗牌。 10 个元素的 shuffle 必须实际执行 28k shuffle 才能获得 10,000 个“好”的。 4 元素 shuffle 实际上具有更高的拒绝率,因为很少的项目(34,000 次拒绝)更容易打破规则。

这并不像随机分布那样让我感兴趣,因为如果这些“改进”引入了偏差,那就不好了。 10k 4 元素分布:

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

使用较小的迭代次数 (1K),您可以看到与修改后的形式相比更均匀的分布。但是,如果您拒绝某些合法的洗牌,这是可以预料的。

十个元素的分布是不确定的,因为有太多的可能性(360 万次洗牌)。也就是说,在 10k 次迭代中,往往会有大约 9980 个系列,其中 12-18 的计数为 2。

【讨论】:

  • 1000 reps - 9 seconds,你测试了多少项目? 10?听起来有点慢。
  • 这是带有列表框、控制台输出和统计信息集合的外部循环。减少 1K==4.2 秒。单次洗牌可能会在内部失败并再次洗牌,单次迭代只有 40 次 Ticks。
  • @Neolisk,不,我搞砸了最初的时间安排......我仍然有一些控制台报告课堂内的拒绝。已更新。
  • 哇,这太棒了。让我进行一些测试。
  • 运行了所有示例,虽然所有示例都非常好,但这是最详细、正确和可扩展的。谢谢!
【解决方案2】:

我相信以下内容将满足给定的要求。我合并了@CoderDennis 对初始随机值的修复,以及传入随机值。我的 VB 技能在 C# 和 JavaScript 中被玷污了太多年,所以对于任何明显的语法错误,我们深表歉意。

它只过滤掉三个连续项目的序列,而不是“(或任意数量的连续原始项目)”。

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

【讨论】:

  • 酷,我去看看。
【解决方案3】:

每个人都在解决您的洗牌问题而忽略了实际问题。

有了这样的约束,我会简单地洗牌,然后测试结果是否符合标准,如果不符合标准,则再次洗牌。不幸的是,这有一个不确定的运行时间,但只要约束不太可能拒绝它,现实世界的性能通常是可以接受的。

但是,在这种特殊情况下,我会采取完全不同的方法。列表中有 4 项,只有 24 种可能的排列,其中 4 种绝对无效。 (我不确定你是否想要 [0, 1, 3, 2] 之类的东西。)因此我会存储列表的所有有效排列,对列表进行排序,从预先计算的列表中选择一个随机排列并相应地“洗牌”列表。

【讨论】:

  • 我考虑过存储有效的排列,这对于 3 或 4 个数字数组来说似乎很好。但如果它达到 10 或 20 个数字数组,那将是一个需要初始化的庞大列表。​​
  • @ToddMain 这就是为什么我说我会在这种情况下采用这种方法——这不是解决问题的一般方法。 n=5 与我认为的一样高,没有很好的理由。
【解决方案4】:

您似乎正在尝试进行 Fisher-Yates 洗牌,但对 Next 的调用不正确。对Next 的调用应该是rnd.Next(ptr + 1)。当您使用 ptr - 1 调用它时,您只会为 4 个项目的序列生成两个排列。起始序列为 [0 1 2 3],其中一个序列是 [1 2 3 0]。有关说明,请参见下表。

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

alt 两次为 0 的原因是 Random.Next(0) 返回 0

编辑:正如 CoderDennis 所指出的,使用 rnd.Next(ptr) 而不是 rnd.Next(ptr + 1) 可能更接近您的要求,因为它将数字移动到新位置会做得更好。当您使用rnd.Next(ptr + 1) 时,您会获得更多排列,但对于每个可能的循环,您可能不会执行任何可能将数字留在其原始位置的交换(取决于序列中的位置和其他交换)。

【讨论】:

    【解决方案5】:

    小集合的简单解决方案:

    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;
    }
    

    【讨论】:

    • 这看起来很吸引人?除了随机化之外,它是否执行上述请求中的一个或两个(1) 在它的原始位置没有任何内容并且 2) 没有 3s 序列)?
    • Where((x, j) =&gt; j != i) 部分确保它的原始位置没有元素。它不能保证在大集合中没有 3 序列,但在小集合中则极不可能。
    【解决方案6】:

    我尚未实施您关于结果永远不会处于其原始位置或结果顺序的规则。一个真正随机的序列无论如何都不会符合这些规则。但是,我确实发现您的实施存在一些问题。我在测试中循环运行ShuffleArray 100 次。下面的代码在产生随机结果方面似乎要好得多。确保在循环调用ShuffleArray 之前创建一次Random 的实例可以消除种子问题。

    另外,您对Next 的调用正在传递ptr - 1,我认为这是不正确的。想想循环的第一次迭代,这就是你的原始代码在做什么:

    1. ptr = items.Lengthptr 设置为 4。
    2. ptr -= 1ptr 设置为 3。
    3. 调用 rnd.Next(ptr - 1) 将选择一个介于 0 和 1 之间的整数。

    这是更新后的代码:

    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
    

    还有一个更简单的版本,使用 For 而不是 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
    

    【讨论】:

    • Next 永远不会用 -1 调用。在循环的最后一次迭代中,ptr 以 2 开始,递减为 1,使用 ptr - 1 或 0 调用 Next,返回 0。
    • 您的第三个选项不正确,因为Random.Next 返回的值小于其参数。所以rnd.Next(ptr - 1) 会在01 之间选择一个整数。
    • @Dmitry 你也是正确的。我已经编辑了答案以反映这一点。
    【解决方案7】:

    在这里,我建议一种方法,您可以通过该方法实现洗牌的目标,将一个数字作为随机数并移动其他数字以填充数组。考虑以下代码:

    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 
    

    项目将包含随机排列的数组, 示例:

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

    更新:

    我的回答不会产生输出0,1,2,3,因为alt = rnd.Next(1, 4) 会产生一个介于 1(lowerLimit) 和 4(upperLimit) 之间的数字。 the Link 表示rnd.Next(lowerLimit,upperLimit) 将产生一个随机数,包括lowerLimit 和排除upperLimit

    基于这个随机数生成即将到来的序列,因为它不会产生0,所以不会产生序列0,1,2,3

    希望答案够清楚

    【讨论】:

    • 如果它在你的例子中产生“1 2 3 0”,那么这不符合要求。
    • @Todd Main : 不,它不会产生0,1,2,3 原因已在答案中更新
    • 这适用于系列规则,但它有一个严重的问题。如果您将结果反馈给它以进行下一次迭代,您将获得前一个系列。结果是它只能创建可能的 24 种组合中的 2 种。
    【解决方案8】:

    你考虑过这样的事情吗?这不会显示任何长于 2 的连续整数字符串。这也不会将任何项目放置在其原始位置。

    注意**如果你制作相同的元素(例如在数组中的 2 个不同位置出现 9),那么可以将 9a 洗牌到 9b 的插槽中,同样,因为洗牌功能不考虑它正在洗牌的值,它只按数组索引洗牌。

    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 
    

    【讨论】:

    • 这看起来很有趣。让我测试一下。
    • 当然,如果有问题,请告诉我,我很确定这可以解决您的所有问题。
    【解决方案9】:

    如果您设置像您这样的标准,则洗牌会失去其随机特征。这是 Knuth shuffle 算法与测试程序的一些实现。我做的两个主要想法是确保 Random 是一个全局变量并从 i 和 N 中选择一个元素,知道 i 是循环的索引,N 是数组的大小。

    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;
          }
        }
    
    
      }  
      }
    

    如果要对一些结果进行元素化,为什么不在两个数组之间实现相似度算法,然后定义一个阈值,如果洗牌后的数组与原始数组的相似度高于阈值,则重新洗牌,虽然我没有建议触摸结果,特别是如果您需要随机洗牌,如果您的算法基于随机洗牌,您将有很多缺陷。

    【讨论】:

      【解决方案10】:

      如果您将问题简化为对索引数组进行洗牌,然后使用该数组对项目数组进行排序,则会容易得多。我创建了一个GuardedShuffle 类,它演示了如何做到这一点。它目前将顺序元素定义为一个,在原始序列中提到附近的值,升序或降序。

      代码是人类对您的洗牌规则的解释,即我将如何手动解决它,用 VB.NET 编写。我没有尝试优化它,但对于合理大小的集合来说,它应该足够快。

      如果您对代码有任何疑问 - 请在 cmets 中告诉我,我会尽力解释 - 尽管我试图保持它干净,但不会对重构和编码标准过于疯狂。

      在将其作为真实应用程序的一部分之前,您可能需要添加一些错误检查。

      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
      

      性能/可扩展性测试(在 100 毫秒内随机播放 1000 个项目):

      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
      

      使用相同的代码,10000 个项目在 7-8 秒内排序。

      【讨论】:

      • 哇,这太棒了!我会对其进行一些测试。
      • @ToddMain:谢谢,我只是想到了一个边缘案例。当数组有两个元素并且最大顺序项 = 1 时,我在 Dim i As Integer 行得到 Index was out of range. Must be non-negative and less than the size of the collection.。这是因为这两个排列都不满足您的要求,{0, 1} 和 {1, 0} 都是顺序的,第一个也将所有项目都放在原来的位置。您可能希望将此行包装在 Try...Catch 块中并抛出一些已知的异常类型,例如 InvalidOperationException 。
      【解决方案11】:

      我相信您所看到的是因为您没有为 Random 类播种一个值。我建议尝试接受 int 作为种子值的构造函数。一个简单的种子值是 DateTime.Now.Ticks 中的 Ticks 数

      有关 Random 类的更多信息,请参见以下链接: http://msdn.microsoft.com/en-us/library/system.random.aspx

      【讨论】:

      • 阅读您提供的链接作为参考 - 默认情况下随机使用 DateTime.Now.Ticks 作为种子。
      • 也许我读错了,但我不认为这就是它所说的。文章写道:“默认情况下,Random 类的无参构造函数使用系统时钟生成其种子值,而其有参构造函数可以根据当前时间的刻度数取一个 Int32 值。”所以是的,无参数构造函数确实使用了时钟,只是没有指定如何使用。它继续说:“......使用无参数构造函数来创建不同的 Random 对象,从而创建产生相同随机数序列的随机数生成器。”
      • 我在过程级别和模块级别都有New Random,结果是一样的。在模块化级别,它应该根据所有来源确保随机性——但这并不是真正的问题。问题是它对数组进行洗牌,结果是{1,2,3,0} 太频繁了(我实际上根本不想要那个 - 或者{2,3,0,1}{3,0,1,2} 就此而言)。
      • @ScopeCreep - 这是因为DateTime.Now.Ticks 可以与所述的“非常接近”相同。我错了,它没有使用DateTime.Now.Ticks,实际上使用的是Environment.Ticks。 Code Here。然而,这并没有改变种子在这里不是问题的事实。
      • @McAden - Touche good sir...touche... 好吧,那么也许代码实际上正在生成随机序列,问题是 4 个项目只有 24 种不同的方式“洗牌”,其中至少有一些是不受欢迎的?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-02
      • 1970-01-01
      • 2022-11-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多