Как получить n-е число в последовательности rand () напрямую, не вызывая rand () n раз?

Насколько я понимаю, установка srand с определенным семенем приводит к тому, что последовательность вызовов rand () каждый раз генерирует одну и ту же серию чисел для этого конкретного семени:

Eg:

             srand(seed1);
             rand() // firstnumber (e.g.: 42)
             rand() // second number (e.g: 17)
             srand(seed1)
             rand() // first number (same as above (42))
             rand() // second number (same as above (17))

Есть ли способ получить n-е число в последовательности напрямую, не вызывая rand () n раз?

  • Например, если мне нужно 17-е случайное число в серии, я хочу получить это число за один вызов, вместо того, чтобы вызывать rand () 17 раз.

  • Я не могу предварительно вычислить и сохранить значения

РЕДАКТИРОВАТЬ: я смотрел эту статью:

https://mathoverflow.net/questions/104915/pseudo-random-algorithm-allowing-o1-computation-of-nth-element

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

РЕДАКТИРОВАТЬ: Причина, по которой я хочу «перейти» к n-му термину, заключается в том, что я использую rand в разных классах с разными начальными числами и продолжаю прыгать вперед и назад между каждым классом. Я хочу, чтобы последовательность в каждом классе продолжалась с того места, где она была остановлена, вместо того, чтобы каждый раз начинать с первого числа. Это однопоточное приложение.

РЕДАКТИРОВАТЬ: при написании сообщения я использовал термин ГПСЧ. Но на самом деле я просто ищу функцию, которая производит случайное число. Я использую это для графики, поэтому проблем с безопасностью нет. Я использую случайные числа для получения небольшого смещения в пикселях.

  • Мне просто нужна быстрая функция.
  • Кажется, что выдает случайные числа, но не обязательно из тех, что используются в приложениях безопасности.
  • Уметь вычислить n-е число за O (1) раз.

Изменить: совершил ошибку - состояния хранения недостаточно. Мне нужно вычислить n-е случайное число последовательно за время O (1). Поскольку в одном классе может быть несколько вызовов для одного и того же n-го члена, сохранения состояния будет недостаточно, и мне нужно вычислить n-й член в O (1)


person Rahul Iyer    schedule 22.02.2014    source источник
comment
Это C или C ++ (или объектный C?). Стандартная библиотека C ++ больше подходит для того, что вы пытаетесь сделать, если я правильно понимаю, что вы пытаетесь сделать.   -  person rici    schedule 22.02.2014
comment
Либо C / C ++, либо Objective-C подойдут. Поскольку мой проект находится в objective-c, я также могу использовать реализации C или C ++.   -  person Rahul Iyer    schedule 22.02.2014
comment
У вас есть поддержка C ++ 11?   -  person Yakk - Adam Nevraumont    schedule 22.02.2014
comment
да. У меня есть поддержка C ++ 11.   -  person Rahul Iyer    schedule 22.02.2014
comment
Поскольку вы говорите, что слабый ГПСЧ в порядке, что-то вроде rand (int seed, int n) {return (seed * n)% 65537; } может работать.   -  person brian beuning    schedule 22.02.2014


Ответы (7)


Сделайте свой собственный ранд и сохраните по одному в каждом классе. Конечно, это самый слабый ГПСЧ. Дело в том, что у вас может быть одновременно несколько активных ГПСЧ.

class Rand {
    int seed;
    const int a = 1103515245;
    const int c = 12345;
public:
    Rand();
    void srand( int );
    int rand();
};

Rand::Rand() : seed(123456789) {}

void Rand::srand( int s ) { seed = s; }

int Rand::rand()
{
  seed = a * seed + c;
  return seed;
}

OP спрашивает: «Я использую rand в разных классах с разными начальными числами». У каждого экземпляра Rand есть собственное семя. Поэтому поместите экземпляр Rand в каждый объект, которому требуется собственное семя.

person brian beuning    schedule 22.02.2014
comment
Это НЕ очень хороший ГПСЧ. Если вам нужны более случайные результаты, обратите внимание на такой алгоритм, как «Твистер Мерсенна». - person Richard J. Ross III; 22.02.2014
comment
Мне не нужно быть непредсказуемо случайным. Просто нужно, чтобы случайность выглядела для человека как шум. Это для создания графики. - person Rahul Iyer; 22.02.2014
comment
Я в основном кодирую на Objective-C, поэтому просто не понимаю, как работает ваш код и как мне его использовать. Похоже, что при каждом вызове rand вы возвращаете одно и то же число. - person Rahul Iyer; 22.02.2014
comment
Rand :: rand () изменяет семя, которое является переменной-членом класса Rand. - person brian beuning; 22.02.2014
comment
Здесь будет очень полезно ‹random› C ++, если ваша платформа поддерживает его. - person user253751; 22.02.2014

Все C ++ 11 PRNG имеют "discard", например

#include <random>
#include <iostream>

int main() {
    std::mt19937 rng;
    static const size_t distance = 5;

    rng.seed(0);
    rng.discard(distance);
    std::cout << "after discard 5: " << rng() << '\n';

    rng.seed(0);
    for (size_t i = 0; i <= distance; ++i) {
        std::cout << i << ": " << rng() << '\n';
    }
}

http://ideone.com/0zeRNq

after discard 5: 3684848379
0: 2357136044
1: 2546248239
2: 3071714933
3: 3626093760
4: 2588848963
5: 3684848379
person kfsone    schedule 22.02.2014
comment
Вам даже не нужен сброс. Каждый класс может иметь свой собственный экземпляр, которого достаточно для (измененного) вопроса - person MSalters; 23.02.2014

Используйте 1_. С этой функцией семя не является глобальным и неявным. Вы передаете начальное число для явного использования, и функция обновляет его по мере вычисления следующего случайного числа. Таким образом, поток случайных чисел каждого класса не зависит от других.


Каждый объект или каждый класс (в зависимости от потребностей вашего дизайна) будет хранить начальное значение в переменной unsigned int. Это инициализирует его; для объектов - в методе init; для занятий в +initialize. Вы можете использовать время или, возможно, /dev/random в качестве начального значения. Если вы инициализируете несколько таких объектов или классов в тесной последовательности, то использование времени - плохая идея, поскольку все они могут происходить в «одно и то же» время (в пределах разрешения используемых вами часов).

После этого каждый раз, когда вам нужен случайный номер, вы звоните rand_r(&yourSeedVariable). Это вернет псевдослучайное значение, вычисленное только из переданного начального числа, без использования какого-либо неявного или глобального состояния. Он использует тот же алгоритм, что и rand(). Он также обновляет начальную переменную, так что при следующем вызове будет выдано следующее случайное число в этой последовательности.

Любой другой объект или класс, использующий тот же метод, будет иметь независимую случайную последовательность. Их вызовы rand_r() не повлияют на вызовы этого объекта или класса, а вызовы этого объекта или класса на них не повлияют. То же самое для всех абонентов rand().


Чтобы уточнить немного дальше. Вы сказали в одной из правок на свой вопрос:

Причина, по которой я хочу «перейти» к n-му члену, заключается в том, что я использую rand в разных классах с разными начальными числами и продолжаю прыгать вперед и назад между каждым классом. Я хочу, чтобы последовательность в каждом классе продолжалась с того места, где она была остановлена, вместо того, чтобы каждый раз начинать с первого числа.

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

person Ken Thomases    schedule 22.02.2014
comment
К сожалению, rand_r ограничен 32-битным состоянием (при условии, что int составляет 32 бита). Это дает вам максимальный период 2 ^ 32, что очень мало и затрудняет получение приличных статистических свойств без отклонения распределения от однородного (что делает период еще хуже). - person R.. GitHub STOP HELPING ICE; 22.02.2014
comment
@ R .. OP сказал, что его не особо интересуют статистические свойства. Однако есть также nrand48() или jrand48(), которые также принимают явный начальный аргумент. - person Ken Thomases; 22.02.2014
comment
Вы можете объяснить, как это работает? Это похоже на то, что я хочу, но я не могу понять, как это использовать. У меня только одна ветка. - person Rahul Iyer; 22.02.2014
comment
Да, мой комментарий предназначен больше для других, читающих вопрос в будущем. - person R.. GitHub STOP HELPING ICE; 22.02.2014

Вам нужен произвольный доступ к набору псевдослучайных потоков. Вы можете получить его, переключившись с std::rand() на блочный шифр в режиме счетчика (CTR) в качестве генератора псевдослучайных чисел. Чтобы прочитать последовательные псевдослучайные числа, зашифруйте последовательные числа в открытом виде. Чтобы читать в другом порядке, зашифруйте числа из того же диапазона в определенном порядке. Тогда каждый класс будет иметь собственное семя, состоящее из ключа и начального значения.

Например, начальное значение одного класса может быть 8675309, а начальное значение - 8008135. Чтобы считать последовательные случайные числа, зашифруйте каждое из 8008136, 8008137, 8008138, 8008139, 8008140, ... этим ключом. Чтобы считать 17-е число в этой последовательности, зашифруйте (8008135 + 17) = 8008152.

person Damian Yerrick    schedule 16.06.2015

Вы можете использовать функцию 1: 1 хеширования на 32-битном или 64-битном счетчике. Для своего хэша вы можете адаптировать любой метод, который PRNG будет использовать в качестве функции обратной связи и / или темперирования, например, этот из xorshift, страница:

uint64_t state;

void srand(uint64_t seed) {
  state = seed;
}

uint64_t hash(uint64_t x) {
  x ^= x >> 12;
  x ^= x << 25;
  x ^= x >> 27;
  return x * 2685821657736338717ull;
}

uint32_t rand(void) {
  return hash(state++) >> 32;
}

uint32_t rand(uint32_t n) {
  return hash(n) >> 32;
}
person sh1    schedule 16.06.2015

Главное в PRNG заключается в том, что (в обычных быстрых реализациях) следующее значение зависит от предыдущего. Так что нет, вы не можете получить N-е значение, не вычислив все предыдущие N-1.

person mike.dld    schedule 22.02.2014
comment
Многие ГПСЧ являются линейными конгруэнтными генераторами, где m - степень двойки. В этих случаях можно рассчитать, каким будет состояние через N поколений, не просматривая каждое из них, как в статье, упомянутой в вопросе. Если x (n + 1) = (a * x (n) + c) mod m, то x (n + k) = (pow (a, k) * x (n) + (pow (a, k) - 1) * c / (a-1)) mod m - person MrBackend; 04.06.2014

Короткий ответ: нет.

Более длинный ответ: псевдослучайные серии являются «случайными» в том смысле, что компьютер не может предварительно вычислить серию, не зная предварительно вычисленный элемент (или начальное число), но являются «псевдо» в том смысле, что последовательность воспроизводима с использованием того же начального числа.

При использовании Google-fu LSFR требует конечного числа состояний. ГПСЧ, которые вы пытаетесь получить, - нет.

person panoptical    schedule 22.02.2014
comment
Все PRNG имеют конечное количество состояний. Кроме того, LFSR - это класс PRNG. - person Dietrich Epp; 22.02.2014
comment
Не знаю, как это правильно. Некоторые псевдослучайные серии определяются явным алгоритмом для предварительного вычисления каждого элемента. Возможно, вы знаете их как блочные шифры. - person Damian Yerrick; 03.03.2017