Генерация случайных чисел в java

Способы записи числа

Представьте, что нам надо записать число 1 миллиард. Самый очевидный путь:

Но в реальной жизни мы обычно опускаем запись множества нулей, так как можно легко ошибиться. Укороченная запись может выглядеть как или для 7 миллиардов 300 миллионов. Такой принцип работает для всех больших чисел.

В JavaScript можно использовать букву , чтобы укоротить запись числа. Она добавляется к числу и заменяет указанное количество нулей:

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

Сейчас давайте запишем что-нибудь очень маленькое. К примеру, 1 микросекунду (одна миллионная секунды):

Записать микросекунду в укороченном виде нам поможет .

Если мы подсчитаем количество нулей , их будет 6. Естественно, верная запись .

Другими словами, отрицательное число после подразумевает деление на 1 с указанным количеством нулей:

Шестнадцатеричные числа широко используются в JavaScript для представления цветов, кодировки символов и многого другого. Естественно, есть короткий стиль записи: , после которого указывается число.

Например:

Не так часто используются двоичные и восьмеричные числа, но они также поддерживаются для двоичных и для восьмеричных:

Есть только 3 системы счисления с такой поддержкой. Для других систем счисления мы рекомендуем использовать функцию (рассмотрим позже в этой главе).

Предсказываем Math.random()

В нем есть задача:

Что нужно вписать вместо вопросов, чтобы функция вернула true? Кажется что это невозможно. Но, это возможно, если вы заглядывали в спеку и видели алгоритм ГПСЧ V8. Решение этой задачи в свое время мне показал Роман Дворнов:

Этот код работал в 70% случаев для Chrome < 49 и Node.js < 5. Рома Дворнов, как всегда, показал чудеса магии, которая не что иное, как глубокое понимание внутренних механизмов браузеров. Я все жду, когда Роман сделает доклад на основе этих событий или напишет более подробную статью.

Видите эти равномерности на левом слайде? Изображение показывает проблему с распределением значений. На картинке слева видно, что значения местами сильно группируются, а местами выпадают большие фрагменты. Как следствие — числа можно предсказать.

Выходит что мы можем отреверсить Math.random() и предсказать, какое было загадано число на основе того, что получили в данный момент времени. Для этого получаем два значения через Math.random(). Затем вычисляем внутреннее состояние по этим значениям. Имея внутреннее состояние можем предсказывать следующие значения Math.random() при этом не меняя внутреннее состояние. Меняем код так так, чтобы вместо следующего возвращалось предыдущее значение. Собственно все это и описано в коде-решении для задачи random4. Но потом алгоритм изменили (подробности читайте в спеке). Его можно будет сломать, как только у нас в JS появится нормальная работа с 64 битными числами. Но это уже будет другая история.

Новый алгоритм выглядит так:

Его все так же можно будет просчитать и предсказать. Но пока у нас нет “длинной математики” в JS. Можно попробовать через TypedArray сделать или использовать специальные библиотеки. Возможно кто-то однажды снова напишет предсказатель. Возможно это будешь ты, читатель. Кто знает 😉

Линейный конгруэнтный ГПСЧ

Линейный конгруэнтный ГПСЧ(LCPRNG) — это распространённый метод для генерации псевдослучайных чисел. Он не обладает криптографической стойкостью. Этот метод заключается в вычислении членов линейной рекуррентной последовательности по модулю некоторого натурального числа m, задаваемой формулой. Получаемая последовательность зависит от выбора стартового числа — т.е. seed. При разных значениях seed получаются различные последовательности случайных чисел. Пример реализации такого алгоритма на JavaScript:

Многие языки программирования используют LСPRNG (но не именно такой алгоритм(!)).

Как говорилось выше, такую последовательность можно предсказать. Так зачем нам ГПСЧ? Если говорить про безопасность, то ГПСЧ — это проблема. Если говорить про другие задачи, то эти свойства — могут сыграть в плюс. Например для различных спец эффектов и анимаций графики может понадобиться частый вызов random. И вот тут важны распределение значений и перформанс! Секурные алгоритмы не могут похвастать скоростью работы.

Еще одно свойство — воспроизводимость. Некоторые реализации позволяют задать seed, и это очень полезно, если последовательность должна повторяться. Воспроизведение нужно в тестах, например. И еще много других вещей существует, для которых не нужен безопасный ГСЧ.

How does Random.js alleviate these problems?

Random.js provides a set of «engines» for producing random integers, which consistently provide values within , i.e. 32 bits of randomness.

One is also free to implement their own engine as long as it returns 32-bit integers, either signed or unsigned.

Some common, biased, incorrect tool for generating random integers is as follows:

The problem with both of these approaches is that the distribution of integers that it returns is not uniform. That is, it might be more biased to return rather than , making it inherently broken.

may more evenly distribute its biased, but it is still wrong. , at least in the example given, is heavily biased to return over .

In order to eliminate bias, sometimes the engine which random data is pulled from may need to be used more than once.

Random.js provides a series of distributions to alleviate this.

Использование генераторов для перебираемых объектов

Некоторое время назад, в главе Перебираемые объекты, мы создали перебираемый объект , который возвращает значения .

Давайте вспомним код:

Мы можем использовать функцию-генератор для итерации, указав её в .

Вот тот же , но с гораздо более компактным итератором:

Это работает, потому что теперь возвращает генератор, и его методы – в точности то, что ожидает :

  • у него есть метод
  • который возвращает значения в виде

Это не совпадение, конечно. Генераторы были добавлены в язык JavaScript, в частности, с целью упростить создание перебираемых объектов.

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

Генераторы могут генерировать бесконечно

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

Конечно, нам потребуется (или ) в цикле по такому генератору, иначе цикл будет продолжаться бесконечно, и скрипт «зависнет».

Генератор – итератор

Как вы, наверно, уже догадались по наличию метода , генератор связан с итераторами. В частности, он является итерируемым объектом.

Его можно перебирать и через :

Заметим, однако, существенную особенность такого перебора!

При запуске примера выше будет выведено значение , затем . Значение выведено не будет. Это потому что стандартный перебор итератора игнорирует на последнем значении, при . Так что результат в цикле не выводится.

Соответственно, если мы хотим, чтобы все значения возвращались при переборе через , то надо возвращать их через :

…А зачем вообще при таком раскладе, если его результат игнорируется? Он тоже нужен, но в других ситуациях. Перебор через – в некотором смысле «исключение». Как мы увидим дальше, в других контекстах очень даже востребован.

Extending

You can add your own methods to instances, as such:

var random = new Random();
random.bark = function() {
  if (this.bool()) {
    return "arf!";
  } else {
    return "woof!";
  }
};
random.bark(); //=> "arf!" or "woof!"

This is the recommended approach, especially if you only use one instance of .

Or you could even make your own subclass of Random:

function MyRandom(engine) {
  return Random.call(this, engine);
}
MyRandom.prototype = Object.create(Random.prototype);
MyRandom.prototype.constructor = MyRandom;
MyRandom.prototype.mood = function() {
  switch (this.integer(, 2)) {
    case :
      return "Happy";
    case 1:
      return "Content";
    case 2:
      return "Sad";
  }
};
var random = new MyRandom();
random.mood(); //=> "Happy", "Content", or "Sad"

Or, if you have a build tool are are in an ES6+ environment:

class MyRandom extends Random {
  mood() {
    switch (this.integer(, 2)) {
      case :
        return "Happy";
      case 1:
        return "Content";
      case 2:
        return "Sad";
    }
  }
}
const random = new MyRandom();
random.mood(); //=> "Happy", "Content", or "Sad"

API

Engines

  • : Utilizes
  • : Utilizes
  • : Utilizes
  • : Produces a new Mersenne Twister. Must be seeded before use.

Or you can make your own!

interface Engine {
  next(): number; // an int32
}

Any object that fulfills that interface is an .

Mersenne Twister API

  • : Seed the twister with an initial 32-bit integer.
  • : Seed the twister with an array of 32-bit integers.
  • : Seed the twister with automatic information. This uses the current Date and other entropy sources.
  • : Produce a 32-bit signed integer.
  • : Discard random values. More efficient than running repeatedly.
  • : Return the number of times the engine has been used plus the number of discarded values.

One can seed a Mersenne Twister with the same value () or values () and discard the number of uses () to achieve the exact same state.

If you wish to know the initial seed of , it is recommended to use the function to create the seed manually (this is what does under-the-hood).

const seed = createEntropy();
const mt = MersenneTwister19937.seedWithArray(seed);
useTwisterALot(mt); // you'll have to implement this yourself
const clone = MersenneTwister19937.seedWithArray(seed).discard(
  mt.getUseCount()
);
// at this point, `mt` and `clone` will produce equivalent values

Distributions

Random.js also provides a set of methods for producing useful data from an engine.

  • : Produce an integer within the inclusive range . can be at its minimum -9007199254740992 (-2 ** 53). can be at its maximum 9007199254740992 (2 ** 53).
  • : Produce a floating point number within the range . Uses 53 bits of randomness.
  • : Produce a boolean with a 50% chance of it being .
  • : Produce a boolean with the specified chance causing it to be .
  • : Produce a boolean with / chance of it being true.
  • : Return a random value within the provided within the sliced bounds of and .
  • : Same as .
  • : Shuffle the provided (in-place). Similar to .
  • : From the array, produce an array with elements that are randomly chosen without repeats.
  • : Same as
  • : Produce an array of length with as many rolls.
  • : Produce a random string using numbers, uppercase and lowercase letters, , and of length .
  • : Produce a random string using the provided string as the possible characters to choose from of length .
  • or : Produce a random string comprised of numbers or the characters of length .
  • : Produce a random string comprised of numbers or the characters of length .
  • : Produce a random within the inclusive range of . and must both be s.

An example of using would be as such:

// create a Mersenne Twister-19937 that is auto-seeded based on time and other random values
const engine = MersenneTwister19937.autoSeed();
// create a distribution that will consistently produce integers within inclusive range .
const distribution = integer(, 99);
// generate a number that is guaranteed to be within  without any particular bias.
function generateNaturalLessThan100() {
  return distribution(engine);
}

Producing a distribution should be considered a cheap operation, but producing a new Mersenne Twister can be expensive.

An example of producing a random SHA1 hash:

// using essentially Math.random()
var engine = nativeMath;
// lower-case Hex string distribution
var distribution = hex(false);
// generate a 40-character hex string
function generateSHA1() {
  return distribution(engine, 40);
}

parseInt и parseFloat

Для явного преобразования к числу можно использовать или . Если строка не является в точности числом, то результат будет :

Единственное исключение — это пробелы в начале строки и в конце, они игнорируются.

В реальной жизни мы часто сталкиваемся со значениями у которых есть единица измерения, например или в CSS. Также во множестве стран символ валюты записывается после номинала . Так как нам получить числовое значение из таких строк?

Для этого есть и .

Они «читают» число из строки. Если в процессе чтения возникает ошибка, они возвращают полученное до ошибки число. Функция возвращает целое число, а возвращает число с плавающей точкой:

Функции вернут , если не смогли прочитать ни одну цифру:

Второй аргумент

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

Usage

In your project, run the following command:

npm install random-js

or

yarn add random-js

In your code:

import{Random}from"random-js";constrandom=newRandom();constvalue=random.integer(1,100);
const{Random}=require("random-js");constrandom=newRandom();constvalue=random.integer(1,100);

Or to have more control:

constRandom=require("random-js").Random;constrandom=newRandom(MersenneTwister19937.autoSeed());constvalue=random.integer(1,100);

It is recommended to create one shared engine and/or instance per-process rather than one per file.

Download and place it in your project, then use one of the following patterns:

define(function(require){var Random =require("random");returnnewRandom.Random(Random.MersenneTwister19937.autoSeed());});define(function(require){var Random =require("random");returnnewRandom.Random();});define("random",function(Random){returnnewRandom.Random(Random.MersenneTwister19937.autoSeed());});

Download and place it in your project, then add it as a tag as such:

<scriptsrc="lib/random-js.min.js"><script><script>var random =newRandom.Random();alert("Random value from 1 to 100: "+random.integer(1,100));</script>

Генератор псевдослучайных чисел и генератор случайных чисел

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

Этот источник используется для накопления энтропии с последующим получением из неё начального значения (initial value, seed), которое необходимо генераторам случайных чисел (ГСЧ) для формирования случайных чисел.

Генератор ПсевдоСлучайных Чисел использует единственное начальное значение, откуда и следует его псевдослучайность, в то время как Генератор Случайных Чисел всегда формирует случайное число, имея в начале высококачественную случайную величину, которая берется из различных источников энтропии.

Выходит, что чтобы создать псевдослучайную последовательность нам нужен алгоритм, который будет генерить некоторую последовательность на основании определенной формулы. Но такую последовательность можно будет предсказать. Тем не менее, давайте пофантазируем, как бы могли написать свой генератор случайных чисел, если бы у нас не было Math.random()

ГПСЧ имеет некоторый алгоритм, который можно воспроизвести.
ГСЧ — это получение чисел полностью из какого либо шума, возможность просчитать который стремится к нулю. При этом в ГСЧ есть определенные алгоритмы для выравнивания распределения.

generator.throw

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

…Но «вернуть» можно не только результат, но и ошибку!

Для того, чтобы передать в ошибку, используется вызов . При этом на строке с возникает исключение.

Например, в коде ниже обращение к внешнему коду завершится с ошибкой:

«Вброшенная» в строке ошибка возникает в строке с . Далее она обрабатывается как обычно. В примере выше она перехватывается и выводится.

Если ошибку не перехватить, то она «выпадет» из генератора. По стеку ближайший вызов, который инициировал выполнение – это строка с . Можно перехватить её там, как и продемонстрировано в примере ниже:

Если же ошибка и там не перехвачена, то дальше – как обычно, либо снаружи, либо она «повалит» скрипт.

Композиция генераторов

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

Разберём композицию на примере.

Пусть у нас есть функция , которая генерирует последовательность чисел:

Мы хотим на её основе сделать другую функцию , которая будет генерировать коды для буквенно-цифровых символов латинского алфавита:

  • – для
  • – для
  • – для

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

Естественно, раз в нашем распоряжении есть готовый генератор , то хорошо бы его использовать.

Конечно, можно внутри запустить несколько раз , объединить результаты и вернуть. Так мы бы сделали с обычными функциями. Но композиция – это кое-что получше.

Она выглядит так:

Здесь использована специальная форма . Она применима только к другому генератору и делегирует ему выполнение.

То есть, при интерпретатор переходит внутрь генератора-аргумента, к примеру, , выполняет его, и все , которые он делает, выходят из внешнего генератора.

Получается – как будто мы вставили код внутреннего генератора во внешний напрямую, вот так:

Код выше по поведению полностью идентичен варианту с . При этом, конечно, переменные вложенного генератора не попадают во внешний, «делегирование» только выводит результаты во внешний поток.

Композиция – это естественное встраивание одного генератора в поток другого. При композиции значения из вложенного генератора выдаются «по мере готовности». Поэтому она будет работать даже если поток данных из вложенного генератора оказался бесконечным или ожидает какого-либо условия для завершения.

Usage

node.js

In your project, run the following command:

npm install random-js

or

yarn add random-js

In your code:

// ES6 Modules
import { Random } from "random-js";
const random = new Random(); // uses the nativeMath engine
const value = random.integer(1, 100);
// CommonJS Modules
const { Random } = require("random-js");
const random = new Random(); // uses the nativeMath engine
const value = random.integer(1, 100);

Or to have more control:

const Random = require("random-js").Random;
const random = new Random(MersenneTwister19937.autoSeed());
const value = random.integer(1, 100);

It is recommended to create one shared engine and/or instance per-process rather than one per file.

Browser using AMD or RequireJS

Download and place it in your project, then use one of the following patterns:

define(function(require) {
  var Random = require("random");
  return new Random.Random(Random.MersenneTwister19937.autoSeed());
});

define(function(require) {
  var Random = require("random");
  return new Random.Random();
});

define("random", function(Random) {
  return new Random.Random(Random.MersenneTwister19937.autoSeed());
});

Browser using tag

Download and place it in your project, then add it as a tag as such:

<script src="lib/random-js.min.js"></script>
<script>
  // Random is now available as a global (on the window object)
  var random = new Random.Random();
  alert("Random value from 1 to 100: " + random.integer(1, 100));
</script>

Why is this needed?

Despite being capable of producing numbers within [0, 1), there are a few downsides to doing so:

  • It is inconsistent between engines as to how many bits of randomness:
    • Internet Explorer: 53 bits
    • Mozilla Firefox: 53 bits
    • Google Chrome/node.js: 32 bits
    • Apple Safari: 32 bits
  • It is non-deterministic, which means you can’t replay results consistently
  • In older browsers, there can be manipulation through cross-frame random polling. This is mostly fixed in newer browsers and is required to be fixed in ECMAScript 6.

Also, and most crucially, most developers tend to use improper and biased logic as to generating integers within a uniform distribution.

Для генераторов – только yield*

Библиотека технически позволяет писать код так:

То есть, можно сделать генератора, его выполнит и передаст значение обратно. Как мы видели выше, библиотека – довольно всеядна. Однако, рекомендуется использовать для вызова функций-генераторов именно .

Причин для этого несколько:

  1. Делегирование генераторов – это встроенный механизм JavaScript. Вместо возвращения значения обратно в , выполнения кода библиотеки… Мы просто используем возможности языка. Это правильнее.
  2. Поскольку не происходит лишних вызовов, это быстрее по производительности.
  3. И, наконец, пожалуй, самое приятное – делегирование генераторов сохраняет стек.

Проиллюстрируем последнее на примере:

При запуске этого кода стек может выглядеть примерно так:

Детали здесь не имеют значения, самое важное – почти весь стек находится внутри библиотеки. Из оригинального скрипта там только одна строка (первая):

Из оригинального скрипта там только одна строка (первая):

То есть, стек говорит, что ошибка возникла в строке :

Что ж, спасибо. Но как мы оказались на этой строке? Об этом в стеке нет ни слова!

Заменим в строке вызов на :

Пример стека теперь:

Если очистить от вспомогательных вызовов, то эти строки – как раз то, что нам надо:

Теперь видно, что (читаем снизу) исходный вызов был в строке , далее – вложенный в строке , и затем произошла ошибка в строке .

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

Итого, рекомендация уже достаточно обоснована – при запуске вложенных генераторов используем .

How does Random.js alleviate these problems?

Random.js provides a set of «engines» for producing random integers, which consistently provide values within , i.e. 32 bits of randomness.

One is also free to implement their own engine as long as it returns 32-bit integers, either signed or unsigned.

Some common, biased, incorrect tool for generating random integers is as follows:

The problem with both of these approaches is that the distribution of integers that it returns is not uniform. That is, it might be more biased to return rather than , making it inherently broken.

may more evenly distribute its biased, but it is still wrong. , at least in the example given, is heavily biased to return over .

In order to eliminate bias, sometimes the engine which random data is pulled from may need to be used more than once.

Random.js provides a series of distributions to alleviate this.

Придумываем свой алгоритм ГПСЧ

Мы можем взять последовательность каких-то чисел и брать от них модуль числа. Самый простой пример, который приходит в голову. Нам нужно подумать, какую последовательность взять и модуль от чего. Если просто в лоб от 0 до N и модуль 2, то получится генератор 1 и 0:

Эта функция генерит нам последовательность 01010101010101… и назвать ее даже псевдослучайной никак нельзя. Чтобы генератор был случайным, он должен проходить тест на следующий бит. Но у нас не стоит такой задачи. Тем не менее даже без всяких тестов мы можем предсказать следующую последовательность, значит такой алгоритм в лоб не подходит, но мы в нужном направлении.

А что если взять какую-то известную, но нелинейную последовательность, например число PI. А в качестве значения для модуля будем брать не 2, а что-то другое. Можно даже подумать на тему меняющегося значения модуля. Последовательность цифр в числе Pi считается случайной. Генератор может работать, используя числа Пи, начиная с какой-то неизвестной точки. Пример такого алгоритма, с последовательностью на базе PI и с изменяемым модулем:

Но в JS число PI можно вывести только до 48 знака и не более. Поэтому предсказать такую последовательность все так же легко и каждый запуск такого генератора будет выдавать всегда одни и те же числа. Но наш генератор уже стал показывать числа от 0 до 9.

Мы получили генератор чисел от 0 до 9, но распределение очень неравномерное и каждый раз он будет генерировать одну и ту же последовательность.

Мы можем взять не число Pi, а время в числовом представлении и это число рассматривать как последовательность цифр, причем для того, чтобы каждый раз последовательность не повторялась, мы будем считывать ее с конца. Итого наш алгоритм нашего ГПСЧ будет выглядеть так:

Вот это уже похоже на генератор псевдослучайных чисел. И тот же Math.random() — это ГПСЧ, про него мы поговорим чуть позже. При этом у нас каждый раз первое число получается разным.

Собственно на этих простых примерах можно понять как работают более сложные генераторы случайных числе. И есть даже готовые алгоритмы. Для примера разберем один из них — это Линейный конгруэнтный ГПСЧ(LCPRNG).

Alternate API

There is an alternate API which may be easier to use, but may be less performant. In scenarios where performance is paramount, it is recommended to use the aforementioned API.

constrandom=newRandom(MersenneTwister19937.seedWithArray(0x12345678,0x90abcdef));constvalue=r.integer(,99);constotherRandom=newRandom();

This abstracts the concepts of engines and distributions.

  • : Produce an integer within the inclusive range . can be at its minimum -9007199254740992 (2 ** 53). can be at its maximum 9007199254740992 (2 ** 53). The special number is never returned.
  • : Produce a floating point number within the range . Uses 53 bits of randomness.
  • : Produce a boolean with a 50% chance of it being .
  • : Produce a boolean with the specified chance causing it to be .
  • : Produce a boolean with / chance of it being true.
  • : Return a random value within the provided within the sliced bounds of and .
  • : Shuffle the provided (in-place). Similar to .
  • : From the array, produce an array with elements that are randomly chosen without repeats.
  • : Same as
  • : Produce an array of length with as many rolls.
  • : Produce a random string using numbers, uppercase and lowercase letters, , and of length .
  • : Produce a random string using the provided string as the possible characters to choose from of length .
  • or : Produce a random string comprised of numbers or the characters of length .
  • : Produce a random string comprised of numbers or the characters of length .
  • : Produce a random within the inclusive range of . and must both be s.

toString(base)

Метод возвращает строковое представление числа в системе счисления .

Например:

может варьироваться от до (по умолчанию ).

Часто используемые:

  • base=16 — для шестнадцатеричного представления цвета, кодировки символов и т.д., цифры могут быть или .

  • base=2 — обычно используется для отладки побитовых операций, цифры или .

  • base=36 — максимальное основание, цифры могут быть или . То есть, используется весь латинский алфавит для представления числа. Забавно, но можно использовать -разрядную систему счисления для получения короткого представления большого числового идентификатора. К примеру, для создания короткой ссылки. Для этого просто преобразуем его в -разрядную систему счисления:

Две точки для вызова метода

Внимание! Две точки в это не опечатка. Если нам надо вызвать метод непосредственно на числе, как в примере выше, то нам надо поставить две точки после числа

Если мы поставим одну точку: , тогда это будет ошибкой, поскольку синтаксис JavaScript предполагает, что после первой точки начинается десятичная часть. А если поставить две точки, то JavaScript понимает, что десятичная часть отсутствует, и начинается метод.

Также можно записать как .

yield – дорога в обе стороны

До этого генераторы наиболее напоминали «итераторы на стероидах». Но, как мы сейчас увидим, это не так, есть фундаментальное различие, генераторы гораздо мощнее и гибче.

Всё дело в том, что – дорога в обе стороны: он не только возвращает результат наружу, но и может передавать значение извне в генератор.

Вызов делает следующее:

  • Возвращает во внешний код, приостанавливая выполнение генератора.
  • Внешний код может обработать значение, и затем вызвать с аргументом: .
  • Генератор продолжит выполнение, аргумент будет возвращён как результат (и записан в ).

Продемонстрируем это на примере:

На рисунке ниже прямоугольником изображён генератор, а вокруг него – «внешний код», который с ним взаимодействует:

На этой иллюстрации показано то, что происходит в генераторе:

  1. Первый вызов – всегда без аргумента, он начинает выполнение и возвращает результат первого («2+2?»). На этой точке генератор приостанавливает выполнение.
  2. Результат переходит во внешний код (в ). Внешний код может выполнять любые асинхронные задачи, генератор стоит «на паузе».
  3. Когда асинхронные задачи готовы, внешний код вызывает с аргументом. Выполнение генератора возобновляется, а выходит из присваивания как результат .

В примере выше – только два .

Увеличим их количество, чтобы стал более понятен общий поток выполнения:

Взаимодействие с внешним кодом:

  1. Первый начинает выполнение… Оно доходит до первого .
  2. Результат возвращается во внешний код.
  3. Второй передаёт обратно в генератор как результат первого и возобновляет выполнение.
  4. …Оно доходит до второго , который станет результатом .
  5. Третий передаёт в генератор как результат второго и возобновляет выполнение, которое завершается окончанием функции, так что .

Получается «пинг-понг»: каждый передаёт в генератор значение, которое становится результатом текущего , возобновляет выполнение и получает выражение из следующего . Исключением является первый вызов , который не может передать значение в генератор, т.к. ещё не было ни одного .

Alternate API

There is an alternate API which may be easier to use, but may be less performant. In scenarios where performance is paramount, it is recommended to use the aforementioned API.

const random = new Random(
  MersenneTwister19937.seedWithArray(0x12345678, 0x90abcdef)
);
const value = r.integer(, 99);

const otherRandom = new Random(); // same as new Random(nativeMath)

This abstracts the concepts of engines and distributions.

  • : Produce an integer within the inclusive range . can be at its minimum -9007199254740992 (2 ** 53). can be at its maximum 9007199254740992 (2 ** 53). The special number is never returned.
  • : Produce a floating point number within the range . Uses 53 bits of randomness.
  • : Produce a boolean with a 50% chance of it being .
  • : Produce a boolean with the specified chance causing it to be .
  • : Produce a boolean with / chance of it being true.
  • : Return a random value within the provided within the sliced bounds of and .
  • : Shuffle the provided (in-place). Similar to .
  • : From the array, produce an array with elements that are randomly chosen without repeats.
  • : Same as
  • : Produce an array of length with as many rolls.
  • : Produce a random string using numbers, uppercase and lowercase letters, , and of length .
  • : Produce a random string using the provided string as the possible characters to choose from of length .
  • or : Produce a random string comprised of numbers or the characters of length .
  • : Produce a random string comprised of numbers or the characters of length .
  • : Produce a random within the inclusive range of . and must both be s.

Плоский асинхронный код

Одна из основных областей применения генераторов – написание «плоского» асинхронного кода.

Общий принцип такой:

  • Генератор не просто значения, а промисы.
  • Есть специальная «функция-чернорабочий» которая запускает генератор, последовательными вызовами получает из него промисы – один за другим, и, когда очередной промис выполнится, возвращает его результат в генератор следующим .
  • Последнее значение генератора () уже обрабатывает как окончательный результат – например, возвращает через промис куда-то ещё, во внешний код или просто использует, как в примере ниже.

Напишем такой код для получения аватара пользователя с github и его вывода, аналогичный рассмотренному в статье про промисы.

Для AJAX-запросов будем использовать метод fetch, он как раз возвращает промисы.

Функция в примере выше – универсальная, она может работать с любым генератором, который промисы.

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

Одна из самых известных – это библиотека co, которую мы рассмотрим далее.

Why is this needed?

Despite being capable of producing numbers within [0, 1), there are a few downsides to doing so:

  • It is inconsistent between engines as to how many bits of randomness:
    • Internet Explorer: 53 bits
    • Mozilla Firefox: 53 bits
    • Google Chrome/node.js: 32 bits
    • Apple Safari: 32 bits
  • It is non-deterministic, which means you can’t replay results consistently
  • In older browsers, there can be manipulation through cross-frame random polling. This is mostly fixed in newer browsers and is required to be fixed in ECMAScript 6.

Also, and most crucially, most developers tend to use improper and biased logic as to generating integers within a uniform distribution.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector