Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Помогите написать числовую функцию (https://javascript.ru/forum/misc/30242-pomogite-napisat-chislovuyu-funkciyu.html)

leny 28.07.2012 20:21

Помогите написать числовую функцию
 
Здравствуйте господа программисты!

Помогите пожалуйста.
Необходимо написать функцию деления числа "A" на несколько случайных, сумма которых в итоге будет числом "A".
Функция должна иметь 3 параметра, первый это число "A", второе - на сколько частей нужно разбить первое и третье - минимальное значение каждого из чисел.

Например:
A = 130
получаем 3 случайных числа, кадое из которых больше 20
57, 32, 41 - сумма равна первому число

Если идея понятна выслушаю предложения и идеи, как это можно реализовать, если кому будет не сложно приму готовое решение.
Заранее благодарен за помощь!

PashPP 28.07.2012 20:53

function funk(a, b, c){
        var part = a/b;
        var q = 0
        for (i=0; i<b-1; i++){
            
            var number = []
             number[i] = Math.floor(Math.random()*part)
             while(number[i] <= c){
                number[i] = Math.floor(Math.random()*part)
             }
             q= q+number[i]
             var lastnam = a - q
                    alert(number[i])
        }
        alert(lastnam)    
    }


Можно так, но это не совсем честный вариант, так как последнее число не случайное.

Dim@ 28.07.2012 21:20

PashPP,
предложу свой вариант (последние число почти случайное)
function Num(num, kol, min){
  if ((kol * min) > num){
    alert("Некорректно введены данные");
    return;
  }
  var kol2 = 0;
  var ar = [];
  var obh = num - kol * min;
  for (var i = 0; i < kol; i++){
    ar[i] = min + Math.round(Math.random() * obh);
    kol2 += ar[i];
  }
  if (kol2 > num){
    Num(num, kol, min);
    return;
  }
  if (kol2 < num) ar[i - 1] += num - kol2;
  alert(ar);
}
Num(130, 3, 20)

leny 28.07.2012 21:22

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

PashPP 28.07.2012 21:46

в идеале все должно быть так, но на деле часто виснет
function funk(a, b, c){
        var part = a-((b-1)*c);
        var q = 0
        for (i=1; i<b; i++){
            
            var number = []
             number[i] = Math.floor(Math.random()*part)
             while(number[i] <= c){
                number[i] = Math.floor(Math.random()*part)
             }

             q= q+number[i]
             part = a - q - (b-i-1)*c
             var lastnam = a - q
                    alert(number[i])
        }
        alert(lastnam)    
    }
    funk(90, 3, 5)

Высчитываем первоначальное максимальное значение числа a-((b-1)*c).
Где а- число, б - количество частей. с - мин. значение части = part.
По ходу выпадания чисел part корректируется в зависимости от выпавших перед ним значений part = a - q - (b-i-1)*c . Где q - cумма всех предыдущих выпаданий чисел.
Мне просто интересно, насколько верен мой подход с программной точки зрения, не разбираюсь еще в этих материях.

PashPP 28.07.2012 21:57

Dim@,
var obh = num - kol * min;
должно же быть
var obh = num - (kol -1)* min;

Не?

PashPP 28.07.2012 22:00

Dim@,
А, нет, извини. Эт оя не понял, что ты делал.

leny 28.07.2012 22:57

Всем спасибо за помощь, предпоследний вариант переделал под свой лад, все работает на ура!

oneguy 28.07.2012 22:59

Вот моё решение, в котором все возможности выпадают с одинаковой вероятностью.
function divide(a, n, m) {
  if (n*m>a)
    return;
  var b=[], d=[], p=a-(m-1)*n-1;
  for (var i=0; i<p; i++)
    b[i]=i; // делаем массив b из чисел 0, 1, ..., p-1
  for (; i>a-m*n; i--) {
    var c=Math.floor(Math.random()*i); // выбираем случайный элемент из b
    d.push(b[c]);
    b[c]=b[i-1]; // удаляем его, считаем, что длина массива b теперь i-1
  }
  d.sort(function (x, y) {
    return x-y;
  });
  d.push(p); // после этой инструкции массив d будет содержать n различных неотрицательных чисел, отсортированных по возрастанию, последнее из которых p. С помощью этого массива формируется выходной массив, для которого будем использовать переменную b заново
  b.length=0;
  b[0]=d[0]+m;
  for (i=0; i<n-1; i++)
    b.push(d[i+1]-d[i]+m-1);
  return b;
}
alert(divide(130, 3, 20));

Ред. Упс, где-то ошибка, выпало 70, -22, 82. Сейчас попробую исправить.
Ред. Уже исправил. Оказывается, метод sort, если не задана сравнительная функция, сортирует элементы как строки.

Дзен-трансгуманист 28.07.2012 23:17

Прикольная задачка. Предыдущие ответы не читал, ибо лень. :p

// предполагается, что все аргументы - целые неотрицательные числа
function SplitNum (sum, summands, min) {

  if (summands < 1 || summands * min > sum) {
    return []; // если операцию выполнить невозможно, возвращаем пустой массив
  }

  var i;
  var range = sum - summands * min; // сумма слагаемых после вычета минимумов
  var points = []; // здесь будут точки деления диапазона [0; range]

  for (i=0; i<summands-1; i++) { // произвольных точек деления на 1 меньше, чем слагаемых

    // генерируем псевдослучайное целое число в диапазоне [0; range]
    points[i] = Math.floor(Math.random() * (range + 1));
  }

  points.push(0, range); // добавляем стартовую и конечную точки деления
  points.sort(function (a, b) { return a - b; }); // сортируем по возрастанию

  var result = []; // сюда будем сгребать слагаемые

  for (i=0; i<summands; i++) {

    // слагаемое вычисляем по разнице между точками деления с добавлением минимума
    result[i] = points[i+1] - points[i] + min;
  }

  return result; // возвращаем массив слагаемых
}

var text = "";

text += SplitNum(130, 3, 20) + "\n";
text += SplitNum(130, 3, 0) + "\n";
text += SplitNum(130, 1, 20) + "\n";
text += SplitNum(100, 10, 10) + "\n";
text += SplitNum(100, 4, 24) + "\n";
text += SplitNum(100500, 10, 10000);

alert(text);


UPD: oneguy опередил меня, потому что комменты зажопил.)) Хотя наши решения немножко отличаются. :)

Dim@ 28.07.2012 23:23

oneguy,
иш какой а :D куда ни глянь ты придешь скажешь есть такие то спецификации и такой-то метод, плюс какие то странные примеры делаешь (я про данный), может быть тебе в интел идти работать - там как раз для случайных чисел делают генератор:lol:

Dim@ 28.07.2012 23:24

oneguy,
делай пожалуйста с комментариями код, а то не понятно ничего:)

Dim@ 28.07.2012 23:35

oneguy,
Дзен-трансгуманист,
ошибся melky всё ещё все думают по разному и у всех все разное :) Quo ire sunt?

oneguy 28.07.2012 23:57

Цитата:

Сообщение от Dim@
oneguy,
делай пожалуйста с комментариями код, а то не понятно ничего

Вообще-то я не очень хороший комментатор кода, но я добавил один комментарий комментарии, так более понятно?
Цитата:

Сообщение от Дзен-трансгуманист
Хотя наши решения немножко отличаются.

Да, отличаются, и не в вашу пользу :lol: У вас результаты могут выпадать не с одинаковой вероятностью. Например, SplitNum(1, 3, 0) выдаёт [0, 1, 0] в 2 раза чаще, чем каждую из остальных 2 возможностей.

melky 29.07.2012 00:05

у всех сделано через Math.random... кто сможет сделать это через композиции ?)

Цитата:

Сообщение от Dim@
ошибся melky всё ещё все думают по разному и у всех все разное Quo ire sunt?

что ?)

oneguy 29.07.2012 00:08

Цитата:

Сообщение от melky
у всех сделано через Math.random... кто сможет сделать это через композиции ?)

Вы предлагаете следать без Math.random, написать свой генератор случайных чисел?

Deff 29.07.2012 00:09

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

melky 29.07.2012 00:13

Цитата:

Сообщение от oneguy
Вы предлагаете следать без Math.random, написать свой генератор случайных чисел?

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

oneguy 29.07.2012 00:29

Цитата:

Сообщение от melky
я видел пример на паскале, где эту задачу решают без рандома.

Интересно, как без рандома? Ведь требуется вывести случайный результат из множества возможных. Покажите пример на Паскале без рандома :)

Дзен-трансгуманист 29.07.2012 00:32

Цитата:

Сообщение от Dim@
всё ещё все думают по разному и у всех все разное Quo ire sunt?

И это очень хорошо. Чем больше возникает вариантов, тем шире поле для взаимодополнений и тем вероятнее, что в итоге образуется наилучшее решение. :)

Цитата:

Сообщение от oneguy
SplitNum(1, 3, 0) выдаёт [0, 1, 0] в 2 раза чаще, чем каждую из остальных 2 возможностей.

function SplitNum (sum, summands, min) {

  if (summands < 1 || summands * min > sum) { return []; }
  var i, range = sum - summands * min, points = [], result = [];
  for (i=0; i<summands-1; i++) { points[i] = Math.floor(Math.random() * (range + 1)); }
  points.push(0, range);
  points.sort(function (a, b) { return a - b; });
  for (i=0; i<summands; i++) { result[i] = points[i+1] - points[i] + min; }
  return result;
}

var counter = [0, 0, 0];

for (var i=0; i<10000; i++) {
  counter[SplitNum(1, 3, 0).indexOf(1)]++;
}

alert(counter);

OH, SHI~ :D

Aetae 29.07.2012 01:28

Цитата:

Сообщение от oneguy (Сообщение 192539)
SplitNum(1, 3, 0) выдаёт [0, 1, 0] в 2 раза чаще, чем каждую из остальных 2 возможностей.

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

Дзен-трансгуманист 29.07.2012 02:30

oneguy,
Я понял, у меня вся фигня в том, что:

points[0, 0] приводит к result[0, 0, 1] (вероятность 25%)
points[1, 1] приводит к result[1, 0, 0] (вероятность 25%)
points[0, 1] и points[1, 0] одинаково приводят к result[0, 1, 0] (вероятность 50%)

function SplitNum (sum, summands, min) {

  if (summands < 1 || summands * min > sum) { return []; }
  var i, range = sum - summands * min, points = [], result = [];
  for (i=0; i<summands-1; i++) { points[i] = Math.floor(Math.random() * (range + 1)); }
  points.push(0, range);
  points.sort(function (a, b) { return a - b; });
  for (i=0; i<summands; i++) { result[i] = points[i+1] - points[i] + min; }
  return result;
}

function testDistribution (range, iterations) {

  var i, counter = [];
  for (i=0; i<range; i++) { counter[i] = 0; }
  for (i=0; i<iterations; i++) { counter[SplitNum(1, range, 0).indexOf(1)]++; }
  return counter;
}

alert(testDistribution(10, 10000));
В идеале распределение должно быть линейным, то есть все числа должны быть близки к 1000, но здесь явно имеем отклонение, близкое к среднеквадратическому.

Тогда просто перемешаем результат перед возвратом:
function SplitNum (sum, summands, min) {

  if (summands < 1 || summands * min > sum) { return []; }
  var i, range = sum - summands * min, points = [], result = [];
  for (i=0; i<summands-1; i++) { points[i] = Math.floor(Math.random() * (range + 1)); }
  points.push(0, range);
  points.sort(function (a, b) { return a - b; });
  for (i=0; i<summands; i++) { result[i] = points[i+1] - points[i] + min; }

  for (i=0; i<summands-1; i++) { // перемешиваем порядок слагаемых
    var j = Math.floor(Math.random() * (summands - i)) + i; // генерируем j в диапазоне [i; summands-1]
    var swap = result[i]; result[i] = result[j]; result[j] = swap; // меняем местами значения по индексам i и j
  }
  return result;
}

function testDistribution (range, iterations) {

  var i, counter = [];
  for (i=0; i<range; i++) { counter[i] = 0; }
  for (i=0; i<iterations; i++) { counter[SplitNum(1, range, 0).indexOf(1)]++; }
  return counter;
}

alert(testDistribution(10, 10000));
Теперь всё в порядке.

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

oneguy 29.07.2012 03:05

Цитата:

Сообщение от Aetae
Нигде не оговорено, что порядок выходных значений тоже должен быть рандомизирован

Если я правильно понял задачу, то случайно выбирается упорядоченный набор чисел. Автор вналаче ведь привёл пример, где выходной массив не был упорядочен по возрастанию или убыванию.
Цитата:

Сообщение от Дзен-трансгуманист
Теперь всё в порядке.

Не в порядке. Возьмите SplitNum(2, 3, 0):
function SplitNum (sum, summands, min) {
 
  if (summands < 1 || summands * min > sum) { return []; }
  var i, range = sum - summands * min, points = [], result = [];
  for (i=0; i<summands-1; i++) { points[i] = Math.floor(Math.random() * (range + 1)); }
  points.push(0, range);
  points.sort(function (a, b) { return a - b; });
  for (i=0; i<summands; i++) { result[i] = points[i+1] - points[i] + min; }
 
  for (i=0; i<summands-1; i++) { // перемешиваем порядок слагаемых
    var j = Math.floor(Math.random() * (summands - i)) + i; // генерируем j в диапазоне [i; summands-1]
    var swap = result[i]; result[i] = result[j]; result[j] = swap; // меняем местами значения по индексам i и j
  }
  return result;
}
 
function testDistribution (iterations) {
 
  var i, counter = [];
  for (i=0; i<6; i++) { counter[i] = 0; }
  for (i=0; i<iterations; i++) {
    var r=SplitNum(2, 3, 0);
    counter[r.indexOf(2)==-1?r.indexOf(0)+3:r.indexOf(2)]++;
  }
  return counter;
}
 
alert(testDistribution(27000));

Есть 6 вариантов: [0, 0, 2], [0, 2, 0], [2, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]. Первые 3 выпадают с вероятностью 4/27, остальные - 5/27.

oneguy 29.07.2012 04:16

Цитата:

Сообщение от Maxmaxmахimus
А числа целые нужны или дробные)?

Целые.

Дзен-трансгуманист 29.07.2012 05:03

Цитата:

Сообщение от oneguy
Есть 6 вариантов: [0, 0, 2], [0, 2, 0], [2, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]. Первые 3 выпадают с вероятностью 4/27, остальные - 5/27.


0002 x1 : 002
0012 x2 : 011
0022 x2 : 002
0112 x1 : 011
0122 x2 : 011
0222 x1 : 002

002 : 1+2+1 = 4x
011 : 2+1+2 = 5x

Да, всё верно. Это естественное распределение, так и должно быть.

function divide(a, n, m) {
  if (n*m>a)
    return;
  var b=[], d=[], p=a-(m-1)*n-1;
  for (var i=0; i<p; i++)
    b[i]=i;
  for (; i>a-m*n; i--) {
    var c=Math.floor(Math.random()*i);
    d.push(b[c]);
    b[c]=b[i-1];
  }
  d.sort(function (x, y) {
    return x-y;
  });
  d.push(p);
  b.length=0;
  b[0]=d[0]+m;
  for (i=0; i<n-1; i++)
    b.push(d[i+1]-d[i]+m-1);
  return b;
}

var time1 = (new Date()).getTime();
divide(10000000, 3, 0);
var time2 = (new Date()).getTime();
alert(((time2-time1)/1000).toFixed(3) + " sec.");
Ой, даааа... Не завидую тому, кто миллиард туда влепит... :lol: Так что ваше решение тоже пока не идеально. ;)

Aetae 29.07.2012 08:53

Длинноник, не вижу рандома вообще.
function RandomSplit( A, parts, min ) {
    var numbers = [];
 
    for ( var i = 0; i < parts; i++ ) {
        var part = ( A / (parts - i) - min) * Math.random() + min;
        part = Math.round( part );
        numbers[i] = part;
        A = A - part;
    }
 
    for ( var i = 0; i < numbers.length; i++ )
        numbers[i] += A / parts;
 
    return numbers;
}
      
var counter = [0, 0, 0];

for (var i=0; i<10000; i++) {
  counter[RandomSplit(1, 3, 0).indexOf(1)]++;
}
 
alert(counter);

melky 29.07.2012 10:16

Цитата:

Сообщение от Maxmaxmахimus
false

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

Dim@ 29.07.2012 11:06

oneguy,
Цитата:

Сообщение от oneguy
Вообще-то я не очень хороший комментатор кода, но я добавил один комментарий комментарии, так более понятно?

Да.
melky,
Цитата:

Сообщение от melky
что ?)

Цитирую
Цитата:

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

:)

oneguy 29.07.2012 12:45

Цитата:

Сообщение от Дзен-трансгуманист
Да, всё верно. Это естественное распределение, так и должно быть.

Не нужно подгонять распределение под ваше решение :) В задаче нужно сделать, чтоб каждая возможность выпадала с одинаковой вероятностью, в данном случае 1/6. Результат [0, 0, 1] является одной возможностью, это в вашем решении он получается 2-мя способами.
Рассуджая таким же образом, можно было оправдать в SplitNum(1, 3, 0) распределение (1/4, 1/2, 1/4), хотя должно быть (1/3, 1/3, 1/3).

oneguy 29.07.2012 14:00

Я написал ещё один алгоритм для первой части функции - составления массива d. Первый алгоритм больше подходит для больших n/p, второй - для маленьких. В функцию добавился 4-ый необязательный параметр - номер алгоритма. Если он отсутствует - алгоритм выбирается автоматически.
function divide(a, n, m, alg) {
  if (n*m>a)
    return;
  var b=[], d=[], p=a-(m-1)*n-1;
  if (alg==1||alg!=2&&20*n>p) {
// алгоритм 1
    for (var i=0; i<p; i++)
      b[i]=i; // делаем массив b из чисел 0, 1, ..., p-1
    while (i>p-n+1) {
      var c=Math.floor(Math.random()*i); // выбираем случайный элемент из b
      d.push(b[c]);
      b[c]=b[--i]; // удаляем его, считаем, что длина массива b теперь i
    }
    d.sort(function (x, y) {
      return x-y;
    });
  } else {
// алгоритм 2
    for (i=0; i<n-1; i++) {
      c=Math.floor(Math.random()*(p-i));
      var x=0, y=i;
      while (x<y) {
        var t=Math.floor((x+y)/2);
        if (d[t]-t>c)
          y=t;
        else x=t+1;
      }
      d.splice(x, 0, c+x);
    }  
  }
// конец алгоритма
  d.push(p); // после этой инструкции массив d будет содержать n различных неотрицательных чисел, отсортированных по возрастанию, последнее из которых p. С помощью этого массива формируется выходной массив, для которого будем использовать переменную b заново
  b.length=0;
  b[0]=d[0]+m;
  for (i=0; i<n-1; i++)
    b.push(d[i+1]-d[i]+m-1);
  return b;
}
//тесты
alert(divide(130, 3, 20)+"\n"+
  divide(100, 4, 25)+"\n"+
  //divide(2000, 1000, 0)+"\n"+
  //divide(100500, 100000, 0)+"\n"+
  divide(1000000000, 3, 0)+"\n");

leny 29.07.2012 16:45

PashPP, а прокомментируйте пожалуйста вот эту часть кода
13 if (kol2 > num){
14 Num(num, kol, min);
15 return;
16 }

Dim@ 29.07.2012 16:50

leny,
если при создании случайных чисел их сумма больше максимального то функция заново выполняется с начальными аргументами и первая функция останавливается
for (var i = 0; i < kol; i++){
  ar[i] = min + Math.round(Math.random() * obh);
  kol2 += ar[i]; // сумма всех псевдо-случайных чисел
}

if (kol2 > num){ //если сумма всех псевдо-случайных чисел
  Num(num, kol, min);//функция вызывается с данными аргументами
  return;// а так как в данной уже нет надобности её останавливаем
}

leny 29.07.2012 17:23

Цитата:

Сообщение от Dim@ (Сообщение 192689)
leny,
если при создании случайных чисел их сумма больше максимального то функция заново выполняется с начальными аргументами и первая функция останавливается
for (var i = 0; i < kol; i++){
  ar[i] = min + Math.round(Math.random() * obh);
  kol2 += ar[i]; // сумма всех псевдо-случайных чисел
}

if (kol2 > num){ //если сумма всех псевдо-случайных чисел
  Num(num, kol, min);//функция вызывается с данными аргументами
  return;// а так как в данной уже нет надобности её останавливаем
}

Просто я заметил одну странную штуку, если второй параметр >= 8, функция частенько не срабатывает. При 8 и 9 иногда срабатывает, а вот при 10 и выше - нет.
Может вы знаете где ошибка?

Dim@ 29.07.2012 17:46

leny,
проблема в том что происходит зависание из-за частого срабатывания if-a - слишком мала вероятность правильного выпадания псевдо-случайных чисел
if (kol2 > num){ //если сумма всех псевдо-случайных чисел
Num(num, kol, min);//функция вызывается с данными аргументами
return;// а так как в данной уже нет надобности её останавливаем
}

leny 29.07.2012 17:53

Цитата:

Сообщение от Dim@
проблема в том что происходит зависание из-за частого срабатывания if-a - слишком мала вероятность правильного выпадания псевдо-случайных чисел

Обойти это никак нельзя?

oneguy 29.07.2012 17:57

Цитата:

Сообщение от leny
Обойти это никак нельзя?

Можете использовать моё решение, выложенное выше в этой теме.

Dim@ 29.07.2012 18:08

leny,
действительно - лучше использовать метод oneguy - плюсы
+более рандомный
+обходит данное препятствие

oneguy 29.07.2012 21:03

Обновил 2-ой алгоритм. Теперь число попадает с первого раза, его не приходится перегенерировать, что делает этот алгоритм быстрее и стабильнее.

oneguy 30.07.2012 01:18

Цитата:

Сообщение от Maxmaxmахimus
это нормально или нужно с этим бороться?

По моему мнению, нужно бороться. Нужно, чтобы распределение результата было равномерное.

Deff 30.07.2012 02:17

Maxmaxmахimus,
по идее число комбинаций целочисленных делителей ограничено
В идеале один раз рандомно выбирать из этого массива всех комбинаций


Часовой пояс GMT +3, время: 15:35.