【问题标题】:How to use Random class to shuffle array in C# [duplicate]如何在 C# 中使用 Random 类对数组进行洗牌 [重复]
【发布时间】: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 算法。

标签: c# arrays sorting random 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]。它的质量取决于您交换元素的次数。

【讨论】:

【解决方案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)。
  • @Enigmativity 这对我来说是关于种子 Random 固有缺陷的新闻。感谢分享。这种偏见似乎在非常大的系列中更为普遍……而不是我们在这个年轻小伙子的硬件任务中处理的这组 5 个。 Yates 算法对我来说不够洗牌。随着集合的每一次通过,它的可用数字池会减少,直到最终 n = 集合大小并且没有随机性。该算法有效,但它的设计更多是为了在非常大的集合上节省 CPU。
  • @JosephTroy - 我想你误解了我的意思。您的算法是错误实现的 Fisher-Yates - 除非您更正它,否则它有偏差。您只需要一次 Fisher-Yates 即可完全随机化 - 无需多次通过。最后,Random 类的偏差与您在答案中的不正确的 Fisher-Yates 引入的偏差相比是微不足道的。
猜你喜欢
  • 2021-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-14
  • 1970-01-01
  • 2017-10-17
  • 2012-03-10
相关资源
最近更新 更多