C # Случайное число

2

Я работаю над проектом, в котором мне нужно сгенерировать 8 случайных чисел. По какой-то причине у меня проблема с частью случайного числа, которая очень трудоемка. Я имею в виду 8 случайных чисел, мне нужна строка длиной 8 символов, состоящая из цифр 0-9. Пример 01234567 или 23716253 и т.д.

Я пробовал цикл 8 раз, генерируя случайное число с Random.Next(0, 9) и просто превращая их в строку и конкатенируя их до последней строки. Я также попытался создать случайное число с использованием Random.Next(0, 99999999) и просто преобразовать число в строку и отложить его до 8 с помощью 0.

Кажется, что оба они довольно медленные, и мне нужно найти более быстрый способ. Я не против делать вызовы на другие языки или что-то еще, если это поможет производительности.

Вот дополнительная информация для добавления. Я не думаю, что им удастся найти что-нибудь суперэффективное. Я должен генерировать это число примерно 50000 раз. Когда я провел тест с 47000, это заняло 8:39 секунды. Это всего лишь 0,011 секунд каждый раз, но это просто замедлялось, потому что im также работает с таблицей. Я также назвал 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();
        }
  • 2
    Пожалуйста, оставьте свой код.
  • 0
    Кажется, что оба метода довольно медленные? Вы не уверены в этом, как насчет получения реальных чисел?
Показать ещё 7 комментариев
Теги:
random

9 ответов

8
Лучший ответ

Маловероятно, что фактическая проблема, которую вы видите, заключается в том, что генерация случайных чисел медленная, а скорее, что вы выполняете ее очень часто. По мере увеличения количества элементов в вашей "кодовой книге" вероятность столкновения с существующим числом также увеличивается. Это приведет к тому, что ваш... while цикл продолжит выполнение снова и снова, пока не найдет что-то доступное.

Я не знаю, сколько данных вы загружаете в кодовую книгу, но если это вообще важно, вам нужно подумать о том, что произойдет с дублирующимися записями.

В вашем случае проблема значительно ухудшается, потому что вы вызываете "новый Random()" внутри цикла. Это приводит к тому, что генератор случайных чисел будет "повторно загружен" со значением, полученным из текущего времени (фактически общее количество миллисекунд с момента загрузки системы). Это означает, что в любое время, когда у вас есть столкновение, цикл будет немедленно повторно выполняться и выберет точно такое же случайное начальное значение, как и раньше, что приведет к тому, что "случайный" номер, который вы создадите, также будет соответствовать ранее проверенному номер. Фактически, в зависимости от скорости вашего устройства, он может повторно воспроизводить одинаковое количество раз.

Самый быстрый обходной путь для этой проблемы, который соответствует структуре вашего текущего кода, - это просто удалить все места, где вы вызываете "новый случайный", и иметь один генератор случайных чисел в начале метода, который вы повторное использование. Это гарантирует, что он не reset каждый раз, когда вы проходите через цикл, и уменьшаете вероятность многократного создания того же числа.

Чтобы действительно исправить это, вам придется больше подумать о случайных числах, которые вы используете. Действительно ли важно, чтобы цифры, которые вы генерируете, были случайными или они просто должны быть уникальными. Кроме того, вы можете генерировать большие случайные числа, чтобы уменьшить вероятность любых дубликатов. Достаточно большое случайное число полностью устранит вероятность дублирования. Крайняя часть этого будет заключаться в использовании 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)

  • 0
    Вы ударяете гвоздь по голове этим. Я переключился на секундомер, чтобы сделать его более точным. Затем я запустил его, ничего не меняя, и получил эти числа. Содержит значение: 00: 03: 05.0966304 Содержит ключ: 00: 01: 16.3948834 Создать ключ: 00: 05: 34.2305136 Добавить: 00: 00: 00.3075949 Затем я переместил = new Random ( ) за пределами цикла и получил эти числа Содержит значение: 00: 03: 02.9969711 Содержит ключ: 00: 00: 00.1570233 Генерировать ключ: 00: 00: 00.2991226 Добавить: 00: 00: 00.1528456 Как вы можете видеть его существенное изменение. Спасибо, я полностью пропустил это.
4

Ни одна из тех вещей, которые вы сказали, которые вы пробовали, должна быть "медленной". Возможно, размещение какого-то кода поможет нам найти то, что неправильно. Кроме того, каковы критерии приемлемости для медленных?

Одна вещь, которую, кажется, вы не пробовали, - это вызвать Random.Next таким образом, чтобы гарантировать, что возвращаемое число равно 8 'chars' long: Random.Next(10000000, 100000000).

  • 0
    Это не сработает, если выходные данные должны иметь такие номера, как «00000001» - это то, что ищет капитан, основываясь на части, которая гласит: «Что я имею в виду под 8 случайными числами, так это то, что мне нужна строка, которая 8 символов, состоящие из цифр 0-9. Пример 01234567 или 23716253 и т. Д. "
3

Глядя на ваш исходный код:

В каждом цикле создается генератор случайных чисел. Создайте его один раз и продолжайте вызывать функцию .Next(). Это

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;
  }   
}
  • 1
    Ура. Я выигрываю :)
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();

(Обратите внимание, что второй параметр для метода "Следующий" не является включительным, поэтому он должен быть 100000000, а не 99999999. Это число фактически отображается назад в строку таким образом, но это не имеет значения, поскольку это случайное число. )

1

Вероятно, в сценарии вы описываете, что строковые операции занимают столько же или больше времени, что и вызовы Random.Next(). Я не тестировал библиотеки, но преобразование двоичного случайного числа в десятичную строку, вероятно, будет намного медленнее, чем генерация. Когда вы генерируете строку по-символу, это, скорее всего, будет иметь место.

Поэтому рассмотрим возможность сохранения числа как int, если это возможно, а затем преобразование только для целей отображения.

  • 0
    Я думаю, что это хорошая возможность. Мне нужно сгенерировать случайные строки, а затем записать их в файл. Я не думаю, что есть какой-то способ без преобразования в строку в какой-то момент.
  • 0
    Предполагая, что вы делаете это даже дистанционно, я не думаю, что было бы возможно записать случайные числа в файл быстрее, чем вы могли бы их сгенерировать. Вывод файла должен быть гораздо более узким местом, чем вычисление.
1

Ну, я решил попробовать победить Гуффа:) Я подозревал, что его версия слишком сильно коснулась. Итак, вот вариант его решения, в котором вместо символьного массива используется массив символов. Он работает примерно в 70% случаев его более быстрого решения, когда я сравнивал его с помощью Stopwatch.

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);
  • 0
    спасибо за это, я вставил его, и это красиво и быстро
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();
    }
0

Похож на ответ Гуффа, но, возможно, быстрее на наномасштабном уровне, так как он избегает "дорогостоящего" разделения.

        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();
  • 1
    Хотя это менее случайно. Цифры 0-5 встречаются чаще, чем цифры 6-9.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню