【问题标题】:Split number into 4 random numbers将数字拆分为 4 个随机数
【发布时间】:2018-10-28 13:05:33
【问题描述】:

我想将10 拆分为一个由 4 个随机数组成的数组,但两者都不能是 0 或高于 4。例如[1,2,3,4][1,4,4,1][4,2,3,1]

我认为这是一个简单的问题,但由于某种原因,我想不出该怎么做。如果有人有一些非常有帮助的指导!

编辑: 这是我现在拥有的代码,但我也生成了一个小于 10 的总数:

  let formation = [];
  let total = 0;

   for (let i = 0; i < 4; i ++) {
    if (total < 9) {
      formation[i] = Math.floor(Math.random() * 4) + 1; 
    } else {
      formation[i] = 1;
    }
  }

【问题讨论】:

  • 一个需求不清楚,到目前为止你尝试过什么?
  • 计算有效组合 - 很少 - 随机选择一个:p
  • 会一直是 10 吗?如果是的话,正如@JaromandaX 所提到的,组合很少。您可以定义所有组合,然后随机选择一个。
  • 你可能想实现一个Partition函数,然后过滤掉所有大于4的分区。
  • 模棱两可的问题。 [1,2,3,4][4,2,3,1] 与 [4,3,2,1]. Do you need [4,4,1,1]` 和 [1,4,4,1] 基本相同......?

标签: javascript arrays algorithm math random


【解决方案1】:

您可以创建所有可能的组合并选择一个随机数组。

function get4() {

    function iter(temp) {
        return function (v) {
            var t = temp.concat(v);
            if (t.length === 4) {
                if (t.reduce(add) === 10) {
                    result.push(t);
                }
                return;
            }
            values.forEach(iter(t));
        };
    }
    
    const
        add = (a, b) => a + b,
        values = [1, 2, 3, 4],
        result = [];

    values.forEach(iter([]));
    return result;
}

console.log(get4().map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }

一种在没有所有可能组合列表的情况下获取随机值的算法

它根据实际总和、索引、下一个索引所需的最小总和以及最大总和,使用随机值的因子和偏移量来工作。

偏移量通常是最小和,或和与最大和之差的较大值。为了获得因子,取三个值作为与随机值相乘的最小值。

该表说明了总和的所有可能值和所需的迭代,基于给定值和获取所有值的迭代。

一开始,总和是小部分分配的值。结果是剩余总和为14 ... 10 的第二个块,因为可以取值1 ... 5。第三轮遵循同样的规则。最后,剩余的总和作为该值的偏移量。


具有1、...、5 值和5 元素的示例,总和为15 和所有可能性:

min:     1
max:     5
length:  5
sum:    15

smin = (length - index - 1) * min
smax = (length - index - 1) * max
offset = Math.max(sum - smax, min)
random = 1 + Math.min(sum - offset, max - offset, sum - smin - min)

    index     sum    sum min  sum max   random   offset
  -------  -------  -------  -------  -------  -------
_      0       15        4       20        5        1
       1       14        3       15        5        1
       1       13        3       15        5        1
       1       12        3       15        5        1
       1       11        3       15        5        1
_      1       10        3       15        5        1
       2       13        2       10        3        3
       2       12        2       10        4        2
       2       11        2       10        5        1
       2       10        2       10        5        1
       2        9        2       10        5        1
       2        8        2       10        5        1
       2        7        2       10        5        1
       2        6        2       10        4        1
_      2        5        2       10        3        1
       3       10        1        5        1        5
       3        9        1        5        2        4
       3        8        1        5        3        3
       3        7        1        5        4        2
       3        6        1        5        5        1
       3        5        1        5        4        1
       3        4        1        5        3        1
       3        3        1        5        2        1
_      3        2        1        5        1        1
       4        5        0        0        1        5
       4        4        0        0        1        4
       4        3        0        0        1        3
       4        2        0        0        1        2
       4        1        0        0        1        1

示例代码采用目标1, ..., 4,长度为4,总和为10

function getRandom(min, max, length, sum) {
    return Array.from(
        { length },
        (_, i) => {
            var smin = (length - i - 1) * min,
                smax = (length - i - 1) * max,
                offset = Math.max(sum - smax, min),
                random = 1 + Math.min(sum - offset, max - offset, sum - smin - min),
                value = Math.floor(Math.random() * random + offset);

            sum -= value;
            return value;
        }
    );
}

console.log(Array.from({ length: 10 }, _ => getRandom(1, 4, 4, 10).join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 谢谢,我选择了这个答案,因为我觉得这是最有效的答案(只需要计算一次所有选项),谢谢!
  • 您的第二个算法比其他算法更频繁地生成一些分区(在示例的情况下为四倍)。
  • @Nina:根据分区算法的经验,我确信这是真的,但我通过实验验证了它以找到实际的偏差。我刚刚运行 geRandom(1, 4, 4, 10) 一百万次,在字典中计算结果。
  • 结果1 1 4 44 4 1 1 是最常见的(大约62,500)。大多数分区的计数约为 15,000,但少数分区为 21,000 或 31,000
  • 那么使用蛮力的方法比后一种更好。谢谢你找到这个。
【解决方案2】:

最简单的解决方案是蛮力。

  1. 创建一个while 循环来嵌套您的计算
  2. 在循环中,创建一个空数组并用随机值填充,直到达到长度
  3. 检查数组的总和是否是您想要的值,如果是则中断循环

上面应该一直运行,直到你有结果。

有两件事值得考虑。

  1. 您可以通过计算轻松测试解决方案是否完全可行,length-of-array 乘以 minimum-value 不超过总和并且 length-of-array 乘以 maximum-value 不小于总和。
  2. 基于随机条件的循环可能会永远运行,因此可能需要最大数量的迭代。

在下面的 sn-p 中考虑了这两点:

function randomNumber(max, min) {
  while (true) {
    var r = Math.round(Math.random() * max);
    if (r >= min) {
      return r;
    }
  }
}

function splitXintoYComponentsBetweenMaxAndMin(numberToSplit, numberOfSplits, maxValue, minValue, onUpdate) {
  if (minValue === void 0) {
    minValue = 1;
  }
  //Test that a result can exist
  if (maxValue * numberOfSplits < numberToSplit || minValue * numberOfSplits > numberToSplit) {
    return new Promise(function(resolve, reject) {
      resolve(false);
    });
  }
  //Create returner array
  var arr = [];
  var accumulator = 0;
  while (arr.length < numberOfSplits) {
    var val = randomNumber(Math.floor(numberToSplit / numberOfSplits), minValue);
    accumulator += val;
    arr.push(val);
  }
  return new Promise(function(resolve, reject) {
    function runTest() {
      var d = Date.now();
      var localMaxValue = Math.min(maxValue, Math.ceil((numberToSplit - accumulator) / 4));
      //Combination loop
      while (accumulator < numberToSplit && Date.now() - d < 17) {
        var index = Math.round(Math.random() * (arr.length - 1));
        if (arr[index] >= maxValue) {
          continue;
        }
        var r = randomNumber(localMaxValue, minValue);
        while (arr[index] + r > maxValue || accumulator + r > numberToSplit) {
          if (Date.now() - d >= 17) {
            break;
          }
          r = randomNumber(localMaxValue, minValue);
        }
        if (arr[index] + r > maxValue || accumulator + r > numberToSplit) {
          continue;
        }
        arr[index] += r;
        accumulator += r;
      }
      if (accumulator < numberToSplit) {
        if (onUpdate !== void 0) {
          onUpdate(arr);
        }
        requestAnimationFrame(runTest);
      } else {
        resolve(arr);
      }
    }
    runTest();
  });
}
//TEST
var table = document.body.appendChild(document.createElement('table'));
table.innerHTML = "<thead><tr><th>Number to split</th><th>Number of splits</th><th>Max value</th><th>Min value</th><th>Run</th></tr></thead>" +
  "<tbody><tr><th><input id=\"number-to-split\" value=\"10\" type=\"number\" min=\"1\"/></th><th><input id=\"number-of-splits\" value=\"4\" type=\"number\" min=\"1\"/></th><th><input id=\"max-value\" type=\"number\" min=\"1\" value=\"4\"/></th><th><input id=\"min-value\" type=\"number\" min=\"1\" value=\"1\"/></th><th><input id=\"run\" type=\"button\" value=\"Run\"/></th></tr></tbody>";
var output = document.body.appendChild(document.createElement('pre'));
output.style.overflowX = "scroll";
document.getElementById("run").onclick = function() {
  splitXintoYComponentsBetweenMaxAndMin(parseInt(document.getElementById("number-to-split").value, 10), parseInt(document.getElementById("number-of-splits").value, 10), parseInt(document.getElementById("max-value").value, 10), parseInt(document.getElementById("min-value").value, 10))
    .then(function(data) {
      if (data !== false) {
        output.textContent += data.join("\t") + '\n';
      } else {
        output.textContent += 'Invalid data\n';
      }
    });
};

编辑 1 - 大计算

使用requestAnimationFramePromises 代码现在可以异步执行,这样可以延长计算时间而不会打扰用户。

我还将random 函数与剩余范围进行了缩放,大大减少了大数字所需的计算量。

【讨论】:

  • 尝试随机数的while循环绝对不是蛮力。蛮力是一种使用系统方法的方法,尝试所有可能的组合,直到找到有效的组合。因此,蛮力最终会找到解决方案(只要至少有一个解决方案),而即使maxIterations 值非常高,您的循环仍可能返回 false。
【解决方案3】:

演出有点晚了,但我发现这是一个有趣的任务,所以你开始吧。我的方法不需要创建所有分区,它也不依赖于找到随机匹配的纯粹运气,它是紧凑的并且应该是无偏见的。

即使使用较大的值,只要max 没有太多限制,它也能有效地工作。

const len = 4;
const total = 10;
const max = 4;

let arr = new Array(len);
let sum = 0;
do {
  // get some random numbers
  for (let i = 0; i < len; i++) {
    arr[i] = Math.random();
  }
  // get the total of the random numbers
  sum = arr.reduce((acc, val) => acc + val, 0);
  // compute the scale to use on the numbers
  const scale = (total - len) / sum;
  // scale the array
  arr = arr.map(val => Math.min(max, Math.round(val * scale) + 1));
  // re-compute the sum
  sum = arr.reduce((acc, val) => acc + val, 0);
  // loop if the sum is not exactly the expected total due to scale rounding effects
} while (sum - total);

console.log(arr);

【讨论】:

    【解决方案4】:

    基本上您需要10 的分区(参见https://en.wikipedia.org/wiki/Partition_(number_theory))并将您的条件应用于结果集。

    // Partition generator taken from 
    // https://gist.github.com/k-hamada/8aa85ac9b334fb89ac4f
    
    function* partitions(n) {
    
        if (n <= 0) throw new Error('positive integer only');
        yield [n];
    
        var x = new Array(n);
        x[0] = n;
        for (var i = 1; i < n; i++) x[i] = 1;
    
        var m = 0, h = 0, r, t;
        while (x[0] != 1) {
            if (x[h] == 2) {
                m += 1;
                x[h] = 1;
                h -= 1;
            } else {
                r = x[h] - 1;
                x[h] = r;
    
                t = m - h + 1;
                while (t >= r) {
                    h += 1;
                    x[h] = r;
                    t -= r;
                }
                m = h + (t !== 0 ? 1 : 0);
                if (t > 1) {
                    h += 1;
                    x[h] = t;
                }
            }
            yield x.slice(0, m + 1);
        }
    }
    
    results = [];
    // Get all possible partitions for your number
    for (var partition of partitions(10)) {
        // Apply your conditions (must be 4 numbers, none of them greater than 4)
        if(partition.length != 4 || partition.some((x) => x > 4)) continue;
        results.push(partition);
    }
    console.log(results);

    【讨论】:

      【解决方案5】:

      鉴于:

      在 n 个总和为 S 的正数的集合中,至少有一个小于 S 除以 n (S/n)

      并且您想要一个正好包含 4 个数字的结果集

      您可以使用以下算法:

      1. 从 [1, floor(S/n)] 范围内获取一个随机数,在本例中 floor(10/4) = 2,因此在 [1,2] 范围内获取一个随机数。让我们将其标记为 x1。
      2. 从范围 [1, floor((S - x1)/(n - 1))] 中获取一个随机数。让我们将其标记为 x2。
      3. 从范围 [1, floor((S - x1 - x2)/(n - 2))] 中获取一个随机数。
      4. 继续直到得到 x(n-1)。
      5. 通过执行 S - x1 - x2 .... - x(n-1) 获取最后一个数字。

      最后,用一个限制随机数上限的条件扩展上述算法。

      n 步中,您可以获得一个集合。

          function getRandomInt(min, max) {
             return Math.floor(Math.random() * (max - min + 1)) + min;
          }
      
          function getRandomCollection(min, max, length, sum) {
              var collection = [];
              var leftSum = sum - (min - 1);
      
              for(var i = 0; i < length - 1; i++) {
                   var number = getRandomInt(min, Math.min(Math.ceil(leftSum/(length - i)), max));
                   leftSum -= number;
                   collection.push(number);
              }
              leftSum += min - 1;
              while(leftSum > max) {
                   var randomIndex = Math.floor(Math.random() * collection.length);
                   if(collection[randomIndex] < max) {
                        collection[randomIndex]++;
                        leftSum--;
                   }
              }
              
              collection.push(leftSum);
              return collection;
          }
          console.log(getRandomCollection(1, 4, 4, 10).join(' + ') + ' = 10');
          console.log(getRandomCollection(3, 20, 10, 100).join(' + ') + ' = 100');

      参考

      My answer using the same algorithm for another question

      【讨论】:

        【解决方案6】:

        这会计算一个从 1 到 4 的随机数

        根据您的需要将其包装在一个函数上以生成数组

        Math.floor(Math.random() * 4) + 1 
        

        var randomNumber = Math.floor(Math.random() * 4) + 1 ;
        console.log(randomNumber);

        【讨论】:

        • 这没有回答问题。
        【解决方案7】:

        这太容易了。

        var values = null;
        while(true) {
            var currentSum = 0;
            var expectedSum = 10;
            values = [];
            while(expectedSum !== currentSum) {
                //var value = Math.floor(Math.random() * 9) + 1;
                var value = Math.floor(Math.random() * 4) + 1;
                if(value + currentSum > expectedSum) {
                    continue;
                }
                currentSum += value;
                values.push(value);
            }
            if(values.length === 4) {
                break;
            } else {
                console.log('false iteration')
            }
        }
        console.log(values);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-03-11
          • 2013-01-13
          • 2018-11-16
          • 2015-11-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-09-28
          相关资源
          最近更新 更多