如何在 C# 中使用 Random 类对数组进行洗牌 [重复]

Posted

技术标签:

【中文标题】如何在 C# 中使用 Random 类对数组进行洗牌 [重复]【英文标题】:How to use Random class to shuffle array in C# [duplicate] 【发布时间】:2021-11-28 21:02:45 【问题描述】:

每个人。所以我今天想练习 C#,但我一直在想如何使用 Random 类来简单地打乱数组

例如这样:

using System;
                    
    public class Program
    
        public static void Main()
        
            int[] arr = 1,2,3,4,5;
        
            Random rand = new Random();
        
            for(int i =0; i < arr.Length; i++)
                int shuffle = rand.Next(arr[i]);
                Console.WriteLine(arr[shuffle]);
            
            
        
    

如您所见,我尝试将 int shuffle = rand.Next(arr[i]); 用作洗牌器,但是 我猜它只是复制了数组中的一些元素。我还是菜鸟,在此先感谢您的回复。

【问题讨论】:

随机不保证你不会得到重复的数字,因为它的名字说它是随机的 您可能想使用 Fisher-Yates shuffle 算法。 【参考方案1】:

您可以尝试实现 Fisher-Yates 洗牌算法:https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

    Random random = new Random();

    ...

    for (int i = 0; i < array.Length - 1; ++i) 
    
        int r = random.Next(i, array.Length);
        (array[r], array[i]) = (array[i], array[r]);
    

我建议为此提取一个方法

// Easiest, but not thread safe
private static Random random = new Random();

private static void Shuffle<T>(T[] array) 

    if (array is null)
        throw new ArgumentNullException(nameof(array));
    
    for (int i = 0; i < array.Length - 1; ++i) 
    
        int r = random.Next(i, array.Length);
        (array[r], array[i]) = (array[i], array[r]);
    
   

您可以通过Main 拨打电话:

Shuffle(array);

您可以使用算法fiddle。

【讨论】:

这很好。我特别喜欢你如何交换数组元素(array[r], array[i]) = (array[i], array[r]);【参考方案2】:

有一些解决方案,但我用它来创建一个随机数组并根据它进行排序

static class Program

    static void Main(string[] args)
    
        var array = new[]  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ;

        Shuffle(ref array);

        foreach (var item in array)
        
            Console.WriteLine(item);
        
        // Fri
        // Wed
        // Sat
        // Thu
        // Mon
        // Sun
        // Tue

    

    static readonly Random rng = new Random();

    public static void Shuffle<T>(ref T[] array)
    
        double[] key = new double[array.Length];
        for (int i = 0; i < key.Length; i++)
        
            key[i] = rng.NextDouble();
        

        Array.Sort(key, array);
    

另一种方法是使用以下 1 行 LINQ 语句

    static void Main(string[] args)
    
        var array = new[]  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ;

        var result = array.OrderBy((item) => rng.NextDouble()).ToArray();

     

【讨论】:

为什么要分配一个新数组以及为什么调用NextDouble()而不是Next()?在我看来,两者都效率低下。 @Enigmativity - NextDouble() 是相等概率所必需的,因为它们在[0,1) 之间具有平坦的概率分布。不幸的是,除非范围是int.MaxValue,否则你不能这样做,这正是NextDouble() 所做的。 A double 使用 8 个字节和 int 4。生成随机 double 的成本会更高 - 并且 Next() 确实将随机值返回给 int.MaxValue @Enigmativity - 你可以选择你想要的,但是你对.Next()的限制。此外,这似乎是问题中未讨论的微优化。【参考方案3】:

最简单的方法是每次获取两个随机数(例如a,b)。然后交换 arr[a] 和 arr[b]。它的质量取决于您交换元素的次数。

【讨论】:

这不是真的。您建议的算法会引入偏差,并且不会均匀分布元素。您需要使用 Fisher-Yates 来正确执行您的建议。 见***.com/questions/67888049/bug-in-nets-random-class【参考方案4】:

这种类型的改组将需要seeding the Randomn 类来调整数组的大小。并且在 for 循环中的每一次通过,它都会将当前数字与数组中的其他随机数字交换。

以下是随机播放的示例:

int[] arr = 1,2,3,4,5;

Random rand = new Random();

for(int i = 0; i < arr.Length; i++)
  int shuffle = rand.Next(arr.Length);
  int n = arr[i];
  arr.SetValue(arr[shuffle], i);
  arr.SetValue(n, shuffle);


Console.Write('[' + arr[0].ToString());
for(int i = 1; i < arr.Length; i++)
  Console.Write("," + arr[i]);

Console.Write("]");

【讨论】:

您可以使用更短的代码:Console.Write($"[string.Join(", ", arr[i])]"); 来提供演示输出 该算法引入了偏差。您必须使用 Fisher-Yates 才能使其正确。 shuffle 的值必须始终等于或大于循环值(在本例中为 i)。 见***.com/questions/67888049/bug-in-nets-random-class @Enigmativity 这对我来说是关于种子 Random 固有缺陷的新闻。感谢分享。这种偏见似乎在非常大的系列中更为普遍……而不是我们在这个年轻小伙子的硬件任务中处理的这组 5 个。 Yates 算法对我来说不够洗牌。随着集合的每一次通过,它的可用数字池会减少,直到最终 n = 集合大小并且没有随机性。该算法有效,但它的设计更多是为了在非常大的集合上节省 CPU。 @JosephTroy - 我想你误解了我的意思。您的算法是错误实现的 Fisher-Yates - 除非您更正它,否则它有偏差。您只需要一次 Fisher-Yates 即可完全随机化 - 无需多次通过。最后,Random 类的偏差与您在答案中的不正确的 Fisher-Yates 引入的偏差相比是微不足道的。

以上是关于如何在 C# 中使用 Random 类对数组进行洗牌 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

当动态值匹配时,jQuery 按类对元素进行分组

如何在 yii2 中使用身份类对管理员和员工进行身份验证

C# 随机数(Random)的使用 编写“班级点名器”

如何显示字节数组十六进制值?

如何打乱一组数的顺序?

如何使用聚类对具有相似意图的句子进行分组?