【问题标题】:How to generate a random number based on fixed character set in javascript如何根据javascript中的固定字符集生成随机数
【发布时间】:2021-01-16 01:39:50
【问题描述】:

我正在生成一个基于固定字符集的数字。

function generator()
{
     var text = "";
     var char_list = "LEDGJR", number_list = "0123456789";
     for(var i=0; i < 2; i++ )
     {  
          text += char_list.charAt(Math.floor(Math.random() * char_list.length));
     }

     for(var j=0; j < 2; j++ )
     {  
          text += number_list.charAt(Math.floor(Math.random() *             
                              number_list.length));
     }

     return text;
}

结果: RE39、JE12 等...

一旦与上述序列相关的所有排列完成,那么生成器应该生成字符串为RE391,JE125意味着在完整的数字上再加一个数字。

如何获取序列的排列计数?

【问题讨论】:

  • 如果你想要3个数字而不是2个,你不能把j &lt; 2改成j &lt; 3吗?
  • 是的,我可以,但不仅仅是改变这一点。仅当使用所有排列时,我才会将 j
  • 所以返回值应该是随机但唯一的,使用过的返回值不应该再次返回,对吗?
  • 这有点挑战性……让我想想。
  • 好的,我有一些想法。回家后会打字,我现在在手机上打字很乏味。

标签: javascript random sequence


【解决方案1】:

为简单起见,考虑以下情况:

chars = "AB"
nums = "123";

我们想要生成一个由两个字符和两个数字组成的 4 位序列。

我们定义这些变量

rows = [chars, chars, nums, nums]
rowSizes = rows.map(row => row.length) // [2, 2, 3, 3]

很容易看出所有可能排列的集合大小等于:

spaceSize = rowSizes.reduce((m, n) => m * n, 1) // 2*2*3*3 = 36

我们定义了两组效用函数,后面我会详细解释它们的用法。

  1. decodeIndex() 这给了我们独特性
function euclideanDivision(a, b) {
  const remainder = a % b;
  const quotient =  (a - remainder) / b;
  return [quotient, remainder]
}

function decodeIndex(index, rowSizes) {
  const rowIndexes = []
  let dividend = index
  for (let i = 0; i < rowSizes.length; i++) {
    const [quotient, remainder] = euclideanDivision(dividend, rowSizes[i])
    rowIndexes[i] = remainder
    dividend = quotient
  }
  return rowIndexes
}
  1. getNextIndex() 这给了我们伪随机性
function isPrime(n) {
  if (n <= 1) return false;
  if (n <= 3) return true;

  if (n % 2 == 0 || n % 3 == 0) return false;

  for (let i = 5; i * i <= n; i = i + 6) {
    if (n % i == 0 || n % (i + 2) == 0) return false;
  }
  return true;
}

function findNextPrime(n) {
  if (n <= 1) return 2;
  let prime = n;

  while (true) {
    prime++;
    if (isPrime(prime)) return prime;
  }
}

function getIndexGeneratorParams(spaceSize) {
  const N = spaceSize;
  const Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
  const firstIndex = Math.floor(Math.random() * spaceSize);
  
  return [firstIndex, N, Q]
}

function getNextIndex(prevIndex, N, Q) {
  return (prevIndex + Q) % N
}

独特性

如上所述,spaceSize 是所有可能排列的数量,因此range(0, spaceSize) 中的每个index 唯一地映射到一个排列。 decodeIndex 有助于此映射,您可以通过以下方式获得index 的对应排列:

function getSequenceAtIndex(index) {
  const tuple = decodeIndex(index, rowSizes)
  return rows.map((row, i) => row[tuple[i]]).join('')
}

伪随机性

(感谢this question。我只是将该代码移植到JS中。)

我们通过轮询“全循环迭代器”获得伪随机性。这个想法很简单:

  1. 将索引0..35布局成一个圆圈,表示上界为N=36
  2. 决定一个步长,表示为Q(在本例中为Q=23),由该公式给出
    Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
  3. 随机决定一个起点,例如号码5
  4. 开始从prevIndex, by
    nextIndex = (prevIndex + Q) % N 生成看似随机的nextIndex

因此,如果我们将5 放入其中,我们将得到(5 + 23) % 36 == 28。输入28,我们得到(28 + 23) % 36 == 15

这个过程会遍历圆圈中的每个数字(在圆圈上的点之间来回跳跃),它只会选择每个数字一次,不重复。当我们回到起点5 时,我们知道我们已经到达终点。

†​​:我不确定这个术语,只是引用this answer
‡:这个公式只给出了一个不错的步长,这会让事情看起来更“随机”,Q 的唯一要求是它必须与 N 互质

完整解决方案

现在让我们将所有部分放在一起。运行 sn -p 看看结果。

我还在每个日志之前添加了一个计数器。对于您使用char_list="LEDGJR", number_list="0123456789" 的情况,4 位序列的spaceSize 应为6*6*10*10 = 3600

您将在 3601 处观察到 5 位序列的日志碰撞 ?

function euclideanDivision(a, b) {
  const remainder = a % b;
  const quotient = (a - remainder) / b;
  return [quotient, remainder];
}

function decodeIndex(index, rowSizes) {
  const rowIndexes = [];
  let divident = index;
  for (let i = 0; i < rowSizes.length; i++) {
    const [quotient, remainder] = euclideanDivision(divident, rowSizes[i]);
    rowIndexes[i] = remainder;
    divident = quotient;
  }
  return rowIndexes;
}

function isPrime(n) {
  if (n <= 1) return false;
  if (n <= 3) return true;

  if (n % 2 == 0 || n % 3 == 0) return false;

  for (let i = 5; i * i <= n; i = i + 6) {
    if (n % i == 0 || n % (i + 2) == 0) return false;
  }
  return true;
}

function findNextPrime(n) {
  if (n <= 1) return 2;
  let prime = n;

  while (true) {
    prime++;
    if (isPrime(prime)) return prime;
  }
}

function getIndexGeneratorParams(spaceSize) {
  const N = spaceSize;
  const Q = findNextPrime(Math.floor((2 * N) / (1 + Math.sqrt(5))));
  const firstIndex = Math.floor(Math.random() * spaceSize);

  return [firstIndex, N, Q];
}

function getNextIndex(prevIndex, N, Q) {
  return (prevIndex + Q) % N;
}

function generatorFactory(rows) {
  const rowSizes = rows.map((row) => row.length);

  function getSequenceAtIndex(index) {
    const tuple = decodeIndex(index, rowSizes);
    return rows.map((row, i) => row[tuple[i]]).join("");
  }

  const spaceSize = rowSizes.reduce((m, n) => m * n, 1);

  const [firstIndex, N, Q] = getIndexGeneratorParams(spaceSize);

  let currentIndex = firstIndex;
  let exhausted = false;

  function generator() {
    if (exhausted) return null;
    const sequence = getSequenceAtIndex(currentIndex);
    currentIndex = getNextIndex(currentIndex, N, Q);
    if (currentIndex === firstIndex) exhausted = true;
    return sequence;
  }

  return generator;
}

function getRows(chars, nums, rowsOfChars, rowsOfNums) {
  const rows = [];
  while (rowsOfChars--) {
    rows.push(chars);
  }
  while (rowsOfNums--) {
    rows.push(nums);
  }
  return rows;
}

function autoRenewGeneratorFactory(chars, nums, initRowsOfChars, initRowsOfNums) {
  let realGenerator;
  let currentRowOfNums = initRowsOfNums;

  function createRealGenerator() {
    const rows = getRows(chars, nums, initRowsOfChars, currentRowOfNums);
    const generator = generatorFactory(rows);
    currentRowOfNums++;
    return generator;
  }

  realGenerator = createRealGenerator();

  function proxyGenerator() {
    const sequence = realGenerator();
    if (sequence === null) {
      realGenerator = createRealGenerator();
      return realGenerator();
    } else {
      return sequence;
    }
  }

  return proxyGenerator;
}

function main() {
  const char_list = "LEDGJR"
  const number_list = "0123456789";
  const generator = autoRenewGeneratorFactory(char_list, number_list, 2, 2);
  let couter = 0
  setInterval(() => {
    console.log(++couter, generator())
  }, 10);
}

main();

【讨论】:

  • 出于演示目的,我保持关闭状态。但是如果这是一个web服务,你只需要持久化firstIndexcurrentIndexcurrentRowOfNums,以便在服务重启时恢复整个内部状态。
  • 算法本身保证了 ID 的唯一性,如果这个生成器是全局唯一的 ID 生成源,则不需要检查 DB 是否有冲突。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-16
  • 2015-09-13
  • 1970-01-01
  • 2015-02-27
  • 1970-01-01
  • 1970-01-01
  • 2011-12-29
相关资源
最近更新 更多