如果没有Thread.Sleep(300),如何在这个类中获得真正的随机性?

我已经创建了一个类(下面的代码)来处理在测试中创建“匹配”测验项,这是输出:

替代文字

它工作正常。

但是,为了使它完全随机 ,我必须让线程在两列的随机改组之间至少hibernate 300次,任何低于300的值都会返回以相同顺序排序的两列,就好像它正在使用随机的相同种子:

LeftDisplayIndexes.Shuffle(); Thread.Sleep(300); RightDisplayIndexes.Shuffle(); 

如果没有这段时间等待,我需要做什么才能完全随机地移动两列?

完整代码:

 using System.Collections.Generic; using System; using System.Threading; namespace TestSort727272 { class Program { static void Main(string[] args) { MatchingItems matchingItems = new MatchingItems(); matchingItems.Add("one", "111"); matchingItems.Add("two", "222"); matchingItems.Add("three", "333"); matchingItems.Add("four", "444"); matchingItems.Setup(); matchingItems.DisplayTest(); matchingItems.DisplayAnswers(); Console.ReadLine(); } } public class MatchingItems { public List Collection { get; set; } public List LeftDisplayIndexes { get; set; } public List RightDisplayIndexes { get; set; } private char[] _numbers = { '1', '2', '3', '4', '5', '6', '7', '8' }; private char[] _letters = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }; public MatchingItems() { Collection = new List(); LeftDisplayIndexes = new List(); RightDisplayIndexes = new List(); } public void Add(string leftText, string rightText) { MatchingItem matchingItem = new MatchingItem(leftText, rightText); Collection.Add(matchingItem); LeftDisplayIndexes.Add(Collection.Count - 1); RightDisplayIndexes.Add(Collection.Count - 1); } public void DisplayTest() { Console.WriteLine(""); Console.WriteLine("--TEST:-------------------------"); for (int i = 0; i < Collection.Count; i++) { int leftIndex = LeftDisplayIndexes[i]; int rightIndex = RightDisplayIndexes[i]; Console.WriteLine("{0}. {1,-12}{2}. {3}", _numbers[i], Collection[leftIndex].LeftText, _letters[i], Collection[rightIndex].RightText); } } public void DisplayAnswers() { Console.WriteLine(""); Console.WriteLine("--ANSWERS:-------------------------"); for (int i = 0; i < Collection.Count; i++) { string leftLabel = _numbers[i].ToString(); int leftIndex = LeftDisplayIndexes[i]; int rightIndex = RightDisplayIndexes.IndexOf(leftIndex); string answerLabel = _letters[rightIndex].ToString(); Console.WriteLine("{0}. {1}", leftLabel, answerLabel); } } public void Setup() { do { LeftDisplayIndexes.Shuffle(); Thread.Sleep(300); RightDisplayIndexes.Shuffle(); } while (SomeLinesAreMatched()); } private bool SomeLinesAreMatched() { for (int i = 0; i < LeftDisplayIndexes.Count; i++) { int leftIndex = LeftDisplayIndexes[i]; int rightIndex = RightDisplayIndexes[i]; if (leftIndex == rightIndex) return true; } return false; } public void DisplayAsAnswer(int numberedIndex) { Console.WriteLine(""); Console.WriteLine("--ANSWER TO {0}:-------------------------", _numbers[numberedIndex]); for (int i = 0; i < Collection.Count; i++) { int leftIndex = LeftDisplayIndexes[i]; int rightIndex = RightDisplayIndexes[i]; Console.WriteLine("{0}. {1,-12}{2}. {3}", _numbers[i], Collection[leftIndex].LeftText, _letters[i], Collection[rightIndex].RightText); } } } public class MatchingItem { public string LeftText { get; set; } public string RightText { get; set; } public MatchingItem(string leftText, string rightText) { LeftText = leftText; RightText = rightText; } } public static class Helpers { public static void Shuffle(this IList list) { Random rng = new Random(); int n = list.Count; while (n > 1) { n--; int k = rng.Next(n + 1); T value = list[k]; list[k] = list[n]; list[n] = value; } } } } 

只创建一个Random类的一个实例。 当你在没有构造函数的情况下调用它时,它会从计算机时钟中获取一个随机种子,因此你可以获得两次相同的种子。

 public static class Helpers { static Random rng = new Random(); public static void Shuffle(this IList list) { int n = list.Count; while (n > 1) { n--; int k = rng.Next(n + 1); T value = list[k]; list[k] = list[n]; list[n] = value; } } } 

移动Random rng = new Random(); 到一个静态变量。

MSDN说“默认种子值来自系统时钟并具有有限的分辨率”。 当您在很小的时间范围内创建许多Random对象时,它们都会获得相同的种子,第一个值将等于所有Random对象。

通过重用相同的Random对象,您将前进到给定种子的下一个随机值。

我必须让线程在两列的随机改组之间至少hibernate300次,任何低于300的值都会返回以相同顺序排序的两列,就像它使用相同的种子进行随机性一样

你在这里回答了自己的问题。 它“似乎是使用相同的种子”,因为它使用相同的种子! 由于Windows系统时钟的粒度相对较粗,因此几乎同时构建的多个Random实例将具有相同的种子值。

正如Albin建议的那样 ,你应该只有一个Random对象并使用它。 这种方式不是一堆伪随机序列都是从同一个种子开始,因此相同,你的Shuffle方法将基于单个伪随机序列。

考虑到您将其作为扩展方法,您可能希望它可以重复使用。 在这种情况下,请考虑使用接受Random的重载和不接受Random的重载:

 static void Shuffle(this IList list, Random random) { // Your code goes here. } static void Shuffle(this IList list) { list.Shuffle(new Random()); } 

这允许调用者提供静态Random对象,如果他/她将连续多次调用Shuffle ; 另一方面,如果它只是一次性的事情, Shuffle可以处理Random实例化本身。

我要指出的最后一件事是,由于解决方案涉及使用单个共享的Random对象,您应该知道Random类不是线程安全的。 如果你有可能同时从多个线程调用Shuffle ,你需要锁定你的Next调用(或者:我更喜欢做的是为每个线程都有一个[ThreadStatic] Random对象,每个对应一个随机值由“核心” Random – 但这涉及更多一些)。

否则你最终可能会突然重新调整一个无穷无尽的零序列。

问题是你创建的Random对象在时间上彼此太靠近了。 当你这样做时,它们的内部伪随机生成器以相同的系统时间播种,它们产生的数字序列将是相同的。

最简单的解决方案是重用单个Random对象,方法是将其作为参数传递给shuffle算法,或者将其存储为实现shuffle的类的成员变量。

粗略地说,随机生成器的工作方式是它们有一个从中导出随机值的种子。 创建新的Random对象时,此种子将设置为当前系统时间,以秒或毫秒为单位。

让我们说当你创建第一个随机对象时,种子是10000.在调用它三次之后,种子是20000,40000,80000,从而生成种子中的任何数字(比方说5,6,2)。 如果你很快创建一个新的Random对象,将使用相同的种子,10000。所以,如果你调用它三次,你将得到相同的种子,20000,40000和80000,以及它们相同的数字。

但是,如果重新使用同一个对象,则最新种子为80000,因此您将生成三个新种子,160000,320000和640000,它们很可能会为您提供新值。

这就是为什么你必须使用一个随机生成器,而不是每次都创建一个新生成器。

尝试使用Random()一次。 你会明白的。