Расширить случайный диапазон от 1–5 до 1–7 - PullRequest
679 голосов
/ 26 сентября 2008

Учитывая функцию, которая производит случайное целое число в диапазоне от 1 до 5, напишите функцию, которая производит случайное целое число в диапазоне от 1 до 7.

  1. Что такое простое решение?
  2. Каково эффективное решение для уменьшения использования памяти или работы на более медленном процессоре?

Ответы [ 76 ]

1 голос
/ 10 сентября 2018

Пришел сюда по ссылке из расширения диапазона поплавков. Этот веселее. Вместо того, как я пришел к выводу, мне пришло в голову, что для данной случайной целочисленной производящей функции f с «основанием» b (4 в этом случае, я расскажу почему), она может быть расширено, как показано ниже:

(b^0 * f() + b^1 * f() + b^2 * f() .... b^p * f()) / (b^(p+1) - 1) * (b-1)

Это преобразует генератор случайных чисел в генератор FLOAT. Я определю 2 параметра здесь b и p. Хотя «основание» здесь равно 4, b на самом деле может быть любым, это также может быть иррациональное число и т. Д. p, я называю точность - это степень того, насколько хорошо вы хотите, чтобы ваш генератор с плавающей запятой был. Думайте об этом как о количестве вызовов, сделанных на rand5 для каждого вызова rand7.

Но я понял, что если вы установите b в значение base + 1 (в данном случае это 4 + 1 = 5), это хорошее место, и вы получите равномерное распределение. Сначала избавьтесь от этого генератора 1-5, это на самом деле rand4 () + 1:

function rand4(){
    return Math.random() * 5 | 0;
}

Чтобы попасть туда, вы можете заменить rand4 на rand5()-1

Далее следует преобразовать rand4 из целочисленного генератора в генератор с плавающей запятой

function toFloat(f,b,p){
    b = b || 2;
    p = p || 3;
    return (Array.apply(null,Array(p))
    .map(function(d,i){return f()})
    .map(function(d,i){return Math.pow(b,i)*d})
    .reduce(function(ac,d,i){return ac += d;}))
    /
    (
        (Math.pow(b,p) - 1)
        /(b-1)
    )
}

Это применит первую написанную мной функцию к данной функции rand. Попробуйте:

toFloat(rand4) //1.4285714285714286 base = 2, precision = 3
toFloat(rand4,3,4) //0.75 base = 3, precision = 4
toFloat(rand4,4,5) //3.7507331378299122 base = 4, precision = 5
toFloat(rand4,5,6) //0.2012288786482335 base = 5, precision =6
...

Теперь вы можете преобразовать этот диапазон с плавающей запятой (0-4 INCLUSIVE) в любой другой диапазон с плавающей запятой, а затем понизить его до целого числа. Здесь наша база 4, потому что мы имеем дело с rand4, поэтому значение b=5 даст вам равномерное распределение. По мере того, как b растет за 4, вы начнете вводить периодические пробелы в распределении. Я протестировал значения b в диапазоне от 2 до 8 с 3000 баллами в каждом и по сравнению с нативным Math.random из javascript, выглядит для меня даже лучше, чем сам нативный:

http://jsfiddle.net/ibowankenobi/r57v432t/

Для ссылки выше, нажмите на кнопку «bin» в верхней части дистрибутивов, чтобы уменьшить размер bin. Последний график является родным Math.random, четвертый, где d = 5, является равномерным.

После того, как вы получите свой диапазон с плавающей запятой, умножьте на 7 и бросьте десятичную часть или умножьте на 7, вычтите 0,5 и округлите:

((toFloat(rand4,5,6)/4 * 7) | 0) + 1   ---> occasionally you'll get 8 with 1/4^6 probability.
Math.round((toFloat(rand4,5,6)/4 * 7) - 0.5) + 1 --> between 1 and 7
1 голос
/ 30 апреля 2012

Первое, что пришло мне в голову, это. Но я понятия не имею, равномерно ли оно распределено. Реализовано в python

случайный импорт

def rand5 ():

return random.randint (1,5)

def rand7 ():

return (((rand5 () -1) * rand5 ())% 7) + 1

1 голос
/ 01 июня 2015

Еще один ответ, который, по-видимому, здесь не освещался:

int rand7() {
  int r = 7 / 2;
  for (int i = 0; i < 28; i++)
    r = ((rand5() - 1) * 7 + r) / 5;
  return r + 1;
}

На каждой итерации r - это случайное значение от 0 до 6 включительно. Это добавляется (основание 7) к случайному значению от 0 до 4 включительно, а результат делится на 5, давая новое случайное значение в диапазоне от 0 до 6 включительно. r начинается с существенного смещения (r = 3 очень смещено!), Но каждая итерация делит это смещение на 5.

Этот метод не совершенно однороден; однако смещение исчезающе мало. Что-то в порядке 1 / (2 ** 64). Что важно в этом подходе, так это то, что он имеет постоянное время выполнения (при условии, что rand5() также имеет постоянное время выполнения). Никаких теоретических опасений, что неудачный вызов может итерировать навсегда, выбрав неверные значения.


Кроме того, саркастический ответ для хорошей меры (намеренно или нет, он был покрыт):

1-5 уже находится в диапазоне 1-7, поэтому допустимая реализация приведена ниже:

int rand7() {
  return rand5();
}

Вопрос не задавался для равномерного распределения.

1 голос
/ 25 мая 2014

Учитывая функцию, которая производит случайное целое число в диапазоне от 1 до 5 rand5(), напишите функцию, которая производит случайное целое число в диапазоне от 1 до 7 rand7()

В предложенном мной решении я звоню только rand5 только один раз

Реальное решение

float rand7()
{
    return (rand5() * 7.0) / 5.0 ;
}

Распределение здесь масштабируется, поэтому оно напрямую зависит от распределения rand5

Целочисленное решение

int rand7()
{
    static int prev = 1;

    int cur = rand5();

    int r = cur * prev; // 1-25

    float f = r / 4.0; // 0.25-6.25

    f = f - 0.25; // 0-6

    f = f + 1.0; // 1-7

    prev = cur;

    return (int)f;
}

Распределение здесь зависит от серии rand7(i) ~ rand5(i) * rand5(i-1)

с rand7(0) ~ rand5(0) * 1

1 голос
/ 29 марта 2014

Я думаю, вы все обдумываете это. Разве это простое решение не работает?

int rand7(void)
{
    static int startpos = 0;
    startpos = (startpos+5) % (5*7);
    return (((startpos + rand5()-1)%7)+1);
}
1 голос
/ 18 марта 2014

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

// Returns 1-5
var rnd5 = function(){
   return parseInt(Math.random() * 5, 10) + 1;
}
// Helper
var lastEdge = 0;
// Returns 1-7
var rnd7 = function () {
  var map = [
     [ 1, 2, 3, 4, 5 ],
     [ 6, 7, 1, 2, 3 ],
     [ 4, 5, 6, 7, 1 ],
     [ 2, 3, 4, 5, 6 ],
     [ 7, 0, 0, 0, 0 ]
  ];
  var result = map[rnd5() - 1][rnd5() - 1];
  if (result > 0) {
    return result;
  }
  lastEdge++;
  if (lastEdge > 7 ) {
    lastEdge = 1;
  }
  return lastEdge;
};

// Test the a uniform distribution
results = {}; for(i=0; i < 700000;i++) { var rand = rnd7(); results[rand] = results[rand] ? results[rand] + 1 : 1;} 
console.log(results)

Результат: [1: 99560, 2: 99932, 3: 100355, 4: 100262, 5: 99603, 6: 100062, 7: 100226]

jsFiddle

1 голос
/ 06 января 2014

Это похоже на @RobMcAfee за исключением того, что я использую магическое число вместо 2-мерного массива.

int rand7() {
    int m = 1203068;
    int r = (m >> (rand5() - 1) * 5 + rand5() - 1) & 7;

    return (r > 0) ? r : rand7();
}
0 голосов
/ 15 октября 2014

Простое решение было хорошо описано: возьмите две random5 выборки для одного random7 результата и повторите его, если результат находится за пределами диапазона, который генерирует равномерное распределение. Если ваша цель состоит в том, чтобы сократить количество вызовов до random5, то это крайне расточительно - среднее число вызовов до random5 для каждого выхода random7 составляет 2,38, а не 2 из-за количества выброшенных выборок.

Вы можете добиться большего успеха, используя больше random5 входов, чтобы генерировать более одного random7 одновременно. Для результатов, рассчитанных с использованием 31-разрядного целого числа, оптимум достигается при использовании 12 вызовов на random5 для генерации 9 random7 выходных данных, принимая в среднем 1,34 вызова на выход. Это эффективно, потому что только 2018983 из 244140625 результатов должны быть списаны, или менее 1%.

Демо в Python:

def random5():
    return random.randint(1, 5)

def random7gen(n):
    count = 0
    while n > 0:
        samples = 6 * 7**9
        while samples >= 6 * 7**9:
            samples = 0
            for i in range(12):
                samples = samples * 5 + random5() - 1
                count += 1
        samples //= 6
        for outputs in range(9):
            yield samples % 7 + 1, count
            samples //= 7
            count = 0
            n -= 1
            if n == 0: break

>>> from collections import Counter
>>> Counter(x for x,i in random7gen(10000000))
Counter({2: 1430293, 4: 1429298, 1: 1428832, 7: 1428571, 3: 1428204, 5: 1428134, 6: 1426668})
>>> sum(i for x,i in random7gen(10000000)) / 10000000.0
1.344606
0 голосов
/ 01 июня 2015

Аналогично ответу Мартина , но гораздо реже выбрасывает энтропию:

int rand7(void) {
  static int m = 1;
  static int r = 0;

  for (;;) {
    while (m <= INT_MAX / 5) {
      r = r + m * (rand5() - 1);
      m = m * 5;
    }
    int q = m / 7;
    if (r < q * 7) {
      int i = r % 7;
      r = r / 7;
      m = q;
      return i + 1;
    }
    r = r - q * 7;
    m = m - q * 7;
  }
}

Здесь мы строим случайное значение между 0 и m-1 и пытаемся максимизировать m, добавляя столько состояний, сколько поместится без переполнения (INT_MAX является наибольшим значением, которое поместится в int в C, или вы можете заменить это любым большим значением, которое имеет смысл в вашем языке и архитектуре.

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

По сравнению с популярными здесь ответами, он вызывает rand5() примерно в два раза чаще.

Деления могут быть разбиты на тривиальные биты и LUT для повышения производительности.

0 голосов
/ 12 мая 2015
def rand5():
    return random.randint(1,5)    #return random integers from 1 to 5

def rand7():
    rand = rand5()+rand5()-1
    if rand > 7:                  #if numbers > 7, call rand7() again
        return rand7()
    print rand%7 + 1

Я думаю, это будет самое простое решение, но везде люди предлагают 5*rand5() + rand5() - 5 как в http://www.geeksforgeeks.org/generate-integer-from-1-to-7-with-equal-probability/. Может кто-нибудь объяснить, что не так с rand5()+rand5()-1

...