【问题标题】:How can I get true randomness in this class without Thread.Sleep(300)?在没有 Thread.Sleep(300) 的情况下,如何在此类中获得真正的随机性?
【发布时间】:2010-09-07 15:52:25
【问题描述】:

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

效果很好。

但是,为了使其完全随机,我必须让 线程休眠 在两列的随机洗牌之间至少 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<MatchingItem> Collection { get; set; }
        public List<int> LeftDisplayIndexes { get; set; }
        public List<int> 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<MatchingItem>();
            LeftDisplayIndexes = new List<int>();
            RightDisplayIndexes = new List<int>();
        }

        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<T>(this IList<T> 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;
            }
        }
    }

}

【问题讨论】:

  • 你应该在谷歌上搜索“随机”,你会惊讶于这是多么困难,并且很难实施......(甚至,关于什么是“真正随机”的协议很少意思)
  • @Charles:我认为他的问题的答案更为基本。他的意思不是数学意义上的“真正随机”,只是“这两列不应该以相同的方式排序”=P.
  • Random minutes in C# 2.0 的可能重复项
  • @Claudiu,是的,我看你是对的.. 他的问题只是对 Random 类的误解/滥用..

标签: c# random


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

    public static class Helpers
    {
        static Random rng = new Random();
        public static void Shuffle<T>(this IList<T> 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;
            }
        }
    }
    

    【讨论】:

    • 注意,如果您正在使用多个线程 - 与大多数 .net Framework 类一样,Random 对象不能保证是线程安全的。
    【解决方案3】:

    我必须让线程休眠 随机数之间至少有 300 个计数 两列的洗牌,任何东西 低于 300 返回两列 以相同的顺序排序,好像它是 使用相同的随机种子

    您已经在这里回答了自己的问题。之所以“好像使用同一个种子”是因为使用同一个种子!由于Windows系统时钟的粒度比较粗,多个Random几乎同时构建的实例将具有相同的种子值。

    作为Albin suggests,您应该只拥有一个Random 对象并使用它。这样一来,您的Shuffle 方法将基于单个伪随机序列,而不是一堆从同一个种子开始并因此相同的伪随机序列。

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

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

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

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

    否则,您可能会突然得到Next,只是重新调整无穷无尽的零序列。

    【讨论】:

    • 通常情况下,不是第一个答案,但肯定是最完整的;)
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

      假设当你创建第一个 Random 对象时,种子是 10000。在调用它 3 次后,种子是 20000、40000、80000,生成种子的任何数字(比如 5、6、2)。如果您非常快速地创建一个新的 Random 对象,将使用相同的种子 10000。因此,如果您调用它 3 次,您将获得相同的种子 20000、40000 和 80000,以及它们的相同数字。

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          猜你喜欢
          • 2014-01-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-09-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多