C#随机数

时间:2009-10-25 02:46:13

标签: c# random

我正在开展一个项目,我需要生成8个随机数。我有一个问题,随机数部分由于某种原因非常耗时。我的意思是8个随机数,我需要一个长度为8个字符的字符串,由数字0-9组成。例01234567或23716253等。

我尝试循环8次,使用Random.Next(0,9)生成一个随机数,然后将它们转换为字符串并将它们连接到最终字符串。我还尝试使用Random.Next(0,99999999)生成一个随机数,只是将数字转换为字符串并用0填充为8。

看起来两者都很慢,我需要想出一个更快的方法。如果它有助于提高性能,我不介意打电话给其他语言或某事。

这是一些额外的信息要添加。我不认为我会找到任何超级高效的东西。我必须生成这个数字大约50000次。当我用47000进行测试时,花了8:39秒。这只是每次.011秒,但它只是减慢了因为即时通讯也在使用有表。我也称为hashtable.ContainsKey()全部47000次,总共花了58秒。这是一个很大的区别。

这是我最初使用的代码。 Convert.ToString(rg.Next(0,99999999))。PadLeft(8,'0');

这是一些试图解决这个问题的代码。这是我得到的时间 包含值:00:00:00.4287102 包含密钥:00:01:12.2539062 生成密钥:00:08:24.2832039 添加:00:00:00

        TimeSpan containsValue = new TimeSpan();
        TimeSpan containsKey = new TimeSpan();
        TimeSpan generateCode = new TimeSpan();
        TimeSpan addCode = new TimeSpan();

        StreamReader sr = new StreamReader(txtDictionaryFile.Text);
        string curWord = sr.ReadLine().ToUpper();
        int i = 1;
        DateTime start;
        DateTime end;
        while (!sr.EndOfStream)
        {
            start = DateTime.Now;
            bool exists = mCodeBook.ContainsValue(curWord);
            end = DateTime.Now;
            containsValue += end - start;
            if (!exists)
            {
                string newCode;
                bool kExists;
                do
                {
                    start = DateTime.Now;
                    Random rnd = new Random(); 
                    StringBuilder builder = new StringBuilder(8); 
                    byte[] b = new byte[8]; 
                    rnd.NextBytes(b); 
                    for (int i = 0; i < 8; i++) 
                    { 
                        builder.Append((char)((b[i] % 10) + 48)); 
                    } 
                    newCode = builder.ToString();
                    end = DateTime.Now;
                    generateCode += end - start;

                    start = DateTime.Now;
                    kExists = mCodeBook.ContainsKey(newCode);
                    end = DateTime.Now;

                    containsKey += end - start;
                }
                while (kExists);

                start = DateTime.Now;
                mCodeBook.Add(newCode, curWord);
                end = DateTime.Now;

                addCode += start - end;
            }
            i++;
            curWord = sr.ReadLine().ToUpper();
        }

9 个答案:

答案 0 :(得分:8)

您看到的实际问题不太可能是随机数生成缓慢,而是您经常执行它。随着“代码簿”中项目数量的增加,现有数字发生冲突的可能性也会增加。这将导致你的do ... while循环一直反复执行直到找到可用的东西。

我不知道您在代码簿中加载了多少数据,但如果它完全没有,您需要考虑重复条目会发生什么。

在你的情况下,问题会变得非常糟糕,因为你在循环中调用“new Random()”。这导致随机数发生器被“重新接种”,其值从当前时间(实际上是自系统启动以来的总毫秒数)得出。这意味着每当你遇到碰撞时,循环将立即重新执行,并将选择与之前完全相同的随机种子值,这将导致您生成的“随机”数字也与先前尝试的相匹配数。实际上,根据机器的速度,它可能会反复产生相同的数字。

这个问题最适合当前代码结构的解决方法是简单地删除所有你称之为“新随机”的地方,并在方法的开头有一个随机数生成器重用。这将确保每次进行循环时都不会重置它,并降低重复生成相同数字的可能性。

要真正解决这个问题,你将不得不考虑你正在使用的随机数。您生成的数字是随机的还是只需要是唯一的非常重要。此外,您可以生成更大的随机数,以降低任何重复的概率。一个足够大的随机数将消除重复的机会。最极端的是使用Guid.NewGuid()。ToString()生成密钥

另外,作为旁注。您显示的性能数字可能无法准确测量正在发生的情况。 DateTime.Now没有足够的“分辨率”来测量像你使用它一样小而快的东西。很多时候,在测试代码中花费的整个时间将小于DateTime.Now的分辨率,这将导致测试的测量时间为零。

例如,当我在我的机器上运行以下测试时:

#define StopTime
using System;
using System.Diagnostics;

class C
{
  static void Main() {
    Random rg = new Random();
#if StopTime
    Stopwatch stopTime = new Stopwatch();
#else
    TimeSpan time = TimeSpan.Zero;
#endif
    for(int i=0;i<1000000;++i) {
#if StopTime
      stopTime.Start();
#else
      DateTime start = DateTime.Now;
#endif
      Convert.ToString(rg.Next(0, 99999999)).PadLeft(8, '0');
#if StopTime
      stopTime.Stop();
#else
      DateTime end = DateTime.Now;
      time += end - start;
#endif
    }
#if StopTime
    Console.WriteLine(stopTime.Elapsed);
#else
    Console.WriteLine(time);
#endif
  }
}

使用DateTime.Now方法测量的时间(00:00:00.7680442)大约是使用高分辨率秒表(00:00:01.6195441)测量的时间的一半

答案 1 :(得分:4)

你说你尝试过的事情都不应该“慢”。也许发布一些代码可以帮助我们找到问题所在。另外,慢的接受标准是什么?

似乎你还没有尝试的一件事是调用Random.Next,以保证返回的数字是8'字符'长:Random.Next(10000000,1000000)。

答案 2 :(得分:3)

查看原始代码:

您在每个循环中创建一个新的随机数生成器。创建一次并继续调用.Next()函数。

答案 3 :(得分:2)

顺便说一句,我只是在我的机器上测试了所提到的方法的相对速度。

在我的机器上,这给出了以下时间:

Testing: Guffa1
00:00:05.2472507
Testing: Guffa2
00:00:03.6620228
Testing: Simple
00:00:03.7890637
Testing: Brian
00:00:01.8473002
Testing: JohnDagg
00:00:03.8853139
Testing: chsh
00:00:05.9960557

唯一真正有用的方法是跳过StringBuilder并直接从字符缓冲区工作

代码如下:

using System;
using System.Text;
using System.Diagnostics;

class C
{
  const int IterationCount = 10000000;

  static void Main() {
    Test("Guffa1", Guffa1);
    Test("Guffa2", Guffa2);
    Test("Simple", Simple);
    Test("Brian", Brian);
    Test("JohnDagg", JohnDagg);
    Test("chsh", chsh);
  }

  delegate string TestDelegate(Random rg);

  private static void Test(string name, TestDelegate testMethod) {
    Console.WriteLine("Testing: " + name);
    Random rg = new Random(0);//Start each test with the same random seed

    //Call the method once outside of the test to make sure the JIT has run etc.
    for(int i=0;i<1000000;++i) {
      testMethod(rg);
    }
    Stopwatch timer = new Stopwatch();
    timer.Start();
    for(int i=0;i<IterationCount;++i) {
      testMethod(rg);
    }
    timer.Stop();
    Console.WriteLine(timer.Elapsed);
  }

  private static string Simple(Random rg) {
    return Convert.ToString(rg.Next(0, 99999999)).PadLeft(8, '0');
  }

  private static string Brian(Random rg) {
    char[] fauxbuilder = new char[8];
    int num = rg.Next(0, 100000000);
    for (int i = 0; i < 8; i++) {
      fauxbuilder[i] = (char)((num % 10) + 48);
      num /= 10;
    }
    return new string(fauxbuilder);
  }

  private static string Guffa1(Random rg) {
    StringBuilder builder = new StringBuilder(8);
    for (int i = 0; i < 8; i++) {
      builder.Append((char)rg.Next(48,58));
    }
    return builder.ToString();
  }

  private static string Guffa2(Random rg) { 
    StringBuilder builder = new StringBuilder(8);
    int num = rg.Next(0, 100000000);
    for (int i = 0; i < 8; i++) {
      builder.Append((char)((num % 10) + 48));
      num /= 10;
    }
    return builder.ToString();
  }

  private static string JohnDagg(Random rg) {
    StringBuilder builder = new StringBuilder(8);
    byte[] b = new byte[8];
    rg.NextBytes(b);
    for (int i = 0; i < 8; i++) {
      builder.Append((char)((b[i] % 10) + 48));
    }
    return builder.ToString();
  }

  private static string chsh(Random rg) {
    return (
      NextSpecial(rg, 10000000) +
      NextSpecial(rg, 1000000) +
      NextSpecial(rg, 100000) +
      NextSpecial(rg, 10000) +
      NextSpecial(rg, 1000) +
      NextSpecial(rg, 100) +
      NextSpecial(rg, 10) +
      NextSpecial(rg, 1))
      .ToString().PadLeft(8,'0');
  }

  static int NextSpecial(Random rg, int multiplier) {
    return rg.Next(0, 10) * multiplier;
  }   
}

答案 4 :(得分:1)

这应该非常有效。它分配一个包含八个字符的缓冲区并为其添加字符。没有额外的步骤将每个数字转换为字符串并连接字符串,字符直接放在缓冲区中。然后缓冲区以字符串的形式返回,因此没有额外的步骤从缓冲区创建字符串:

StringBuilder builder = new StringBuilder(8);
for (int i = 0; i < 8; i++) {
   builder.Append((char)rnd.Next(48,58));
}
string code = builder.ToString();

将单个随机数转换为字符串的优点是只调用一次随机生成器。您可以通过自己转换为字符串来加快速度:

StringBuilder builder = new StringBuilder(8);
int num = rnd.Next(0, 100000000);
for (int i = 0; i < 8; i++) {
   builder.Append((char)((num % 10) + 48));
   num /= 10;
}
string code = builder.ToString();

(注意,Next方法的第二个参数不包括在内,所以它应该是100000000而不是99999999.这个数字实际上是以这种方式向后渲染到字符串中,但这并不重要,因为它是一个随机数。 )

答案 5 :(得分:1)

在您描述的场景中,字符串操作可能占用与Random.Next()的调用相同或更多的时间。我没有测试过库,但是将二进制随机数转换为十进制字符串可能比生成它要慢得多。当你逐个字符地生成字符串时,更有可能出现这种情况。

因此,如果可能,请考虑将数字保留为int,然后转换仅用于显示目的。

答案 6 :(得分:1)

好吧,我决定尝试击败Guffa :)我怀疑他的版本有太多的间接性。所以,这是他的解决方案的变体,它使用字符数组而不是字符串构建器。当我通过Stopwatch对其进行基准测试时,它在大约70%的时间里都可以运行。

char[] fauxbuilder = new char[8];
int num = rnd.Next(0, 100000000);
for (int i = 0; i < 8; i++)
{
    fauxbuilder[i] = (char)((num % 10) + 48);
    num /= 10;
}
string code = new string(fauxbuilder);

答案 7 :(得分:0)

类似于Guffa的答案,但在纳米级别上可能更快,因为它避免了“代价高昂”的分裂。

        Random rnd = new Random();
        StringBuilder builder = new StringBuilder(8);
        byte[] b = new byte[8];
        rnd.NextBytes(b);
        for (int i = 0; i < 8; i++)
        {
            builder.Append((char)((b[i] % 10) + 48));
        }
        string code = builder.ToString();

答案 8 :(得分:0)

此代码是我作为解决方案一起入侵的,并且没有真正的优化(除了将列表初始化为已知大小)。 在我的系统上重复测试它是1秒钟。我把它写在最后用于质量控制目的。它似乎产生了你正在寻找的东西,但随时可以指出任何缺陷。

    static Random rand = new Random();

    static int NextSpecial(this Random r, int multiplier)
    {
        return r.Next(0, 10) * multiplier;
    }

    static string randomString()
    {
        return (rand.NextSpecial(10000000) +
               rand.NextSpecial(1000000) +
               rand.NextSpecial(100000) +
               rand.NextSpecial(10000) +
               rand.NextSpecial(1000) +
               rand.NextSpecial(100) +
               rand.NextSpecial(10) +
               rand.NextSpecial(1))
               .ToString().PadLeft(8,'0');
    }

    static void Main()
    {
        int MAXITEMS = 1000000;
        IList<string> numbers = new List<string>(MAXITEMS);
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < MAXITEMS; i++)
        {
            numbers.Add(randomString());
        }
        sw.Stop();

        Console.WriteLine("{0} iterations took: {1}", MAXITEMS.ToString(), sw.Elapsed);

        File.WriteAllLines(@"c:\test.txt", numbers.ToArray());
        Console.ReadLine();
    }