如何生成一个范围内的随机数但排除一些随机数?

发布于 2024-10-29 04:29:19 字数 342 浏览 4 评论 0 原文

基本上我选择一个 0-24 之间的随机数:

Math.floor(Math.random() * myArray.length); // myArray contains 25 items

假设它是 8。现在我想得到另一个 0-24 范围内的数字,但这一次,我不想要 8。下一次,我可能会掷出 15。现在我想再次掷出,但我不想要 8 或 15。我现在处理这个问题的方法是使用 do while 循环,如果数字相同,我就重新掷出。

这是我作业的一小部分,事实上,我已经让它满足了所有要求,所以我想你可以说这是为了我个人的利益,这样我就可以正确地写这个,而不是最终陷入“每日的wtf” ”。

Basically I pick a random number between 0-24:

Math.floor(Math.random() * myArray.length); // myArray contains 25 items

Lets say it comes out to be 8. Now I want to get another number in the same range 0-24 but this time, I do not want an 8. The next time, I might roll a 15. Now I want to roll again but I don't want an 8 or 15. The way I am handling this now is by using do while loops and if the number comes out the same, I just reroll.

This is a small portion of my homework and I, in fact, have it working to meet all the requirements so I guess you could say this is for my own personal benefit so I can write this properly and not end up on "the daily wtf".

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

滥情空心 2024-11-05 04:29:19

设置一个包含所有值的数组(如果您只处理较小的数字,例如示例中的 25,这只是一个有效的选项),如下所示:

var array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];

然后,选择 0 到 0 之间的随机数数组长度:

var num = Math.floor(Math.random() * array.length);

从数组中删除该索引号:

var roll = array.splice(num, 1);

Javascript splice() 从数组中删除索引项并将该项作为数组返回。非常适合您的使用。

从卷中取出第一个索引,因为无论如何我们只切掉 1 个:

var yourNumber = roll[ 0 ];

根据需要继续执行任意数量的卷。另外,您可能希望将原始数组存储为副本,以便可以轻松“重置”数字。

Set an array with all the values (this is only a valid option if you're only doing small numbers, like the 25 in your example), like this:

var array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];

then, pick a random number between 0 and the array length:

var num = Math.floor(Math.random() * array.length);

remove that index number from the array:

var roll = array.splice(num, 1);

Javascript splice() removes indexed items from an array and returns the item(s) as an array. Perfect for your use.

Grab the first index from the roll, since we only cut 1 out anyway:

var yourNumber = roll[ 0 ];

Keep doing for as many rolls as you want. Also, you might want to store the original array as a copy so that you can "reset" the numbers easily.

一世旳自豪 2024-11-05 04:29:19

这很容易,伙计们。您不希望对此进行递归。这些答案真的很糟糕。理想情况下,您也不想对数组进行硬编码。

function getRandomWithOneExclusion(lengthOfArray,indexToExclude){

  var rand = null;  //an integer

    while(rand === null || rand === indexToExclude){
       rand = Math.round(Math.random() * (lengthOfArray - 1));
    }

  return rand;
}

现在使用上面函数返回的值从您想要的任何数组中选择一个元素,就像这样:

var arr = [];
var random = getRandomWithOneExclusion(arr.length,5);  //array has length x, we want to exclude the 5th element
var elem = arr[random];

就是这样。如果您想排除多个值,那么您必须使其更加复杂,但对于排除一个值,这很有效。对此的递归解决方案是矫枉过正并且是一个坏主意。

我还没有测试过这一点,但要排除多个元素,请尝试以下操作:

function getRandomWithManyExclusions(originalArray,arrayOfIndexesToExclude){

   var rand = null;

   while(rand === null || arrayOfIndexesToExclude.includes(rand)){
         rand = Math.round(Math.random() * (originalArray.length - 1));
    }
     return rand;
  }

上述方法听起来与OP的原始方法并没有太大不同。此方法可以正常工作,因为它不会以有偏差的方式从数组中进行采样。

This is easy guys. You do not want recursion for this one. These answers are really bad. Ideally you do not want to hardcode the array, either.

function getRandomWithOneExclusion(lengthOfArray,indexToExclude){

  var rand = null;  //an integer

    while(rand === null || rand === indexToExclude){
       rand = Math.round(Math.random() * (lengthOfArray - 1));
    }

  return rand;
}

now use the value returned from the above function to choose an element from whatever array you want, just like so:

var arr = [];
var random = getRandomWithOneExclusion(arr.length,5);  //array has length x, we want to exclude the 5th element
var elem = arr[random];

that's it. if you wanted to exclude more than value, then you would have to make this more sophisticated, but for excluding one value, this works well. A recursive solution for this is overkill and a bad idea.

I haven't tested this, but to exclude more than one element, try this:

function getRandomWithManyExclusions(originalArray,arrayOfIndexesToExclude){

   var rand = null;

   while(rand === null || arrayOfIndexesToExclude.includes(rand)){
         rand = Math.round(Math.random() * (originalArray.length - 1));
    }
     return rand;
  }

The above method does not sound too different from the OP's original method. This method works properly because it does not sample in a biased way from the array.

原来分手还会想你 2024-11-05 04:29:19

假设您需要从 1...5 范围内选择一个随机数并排除值 2, 4 那么:

  • 1 范围内选择一个随机数...3
  • 对排除数列表进行排序
  • 对于每个小于/等于随机数的排除数:随机数加一
function getRandomExcept(min, max, except) {
  except.sort(function(a, b) {
    return a - b;
  });
  var random = Math.floor(Math.random() * (max - min + 1 - except.length)) + min;
  var i;
  for (i = 0; i < except.length; i++) {
    if (except[i] > random) {
      break;
    }
    random++;
  }
  return random;
}

/*
 * Test iterations. Make sure that:
 * excluded numbers are skipped 
 * numbers are equally distributed
 */
(function(min, max, except) {
  var iterations = 1000000;
  var i;
  var random;
  var results = {};
  for (i = 0; i < iterations; i++) {
    random = getRandomExcept(min, max, except);
    results[random] = (results[random] || 0) + 1;
  }
  for (random in results) {
    console.log("value: " + random + ", count: " + results[random] + ", percent: " + results[random] * 100 / iterations + "%");
  }
})(1, 5, [2, 4]);

Suppose you need to choose a random number from the range 1...5 and exclude the values 2, 4 then:

  • Pick a random number from the range 1...3
  • Sort excluded number list
  • For each excluded number less than/equal to the random number: add one to the random number

function getRandomExcept(min, max, except) {
  except.sort(function(a, b) {
    return a - b;
  });
  var random = Math.floor(Math.random() * (max - min + 1 - except.length)) + min;
  var i;
  for (i = 0; i < except.length; i++) {
    if (except[i] > random) {
      break;
    }
    random++;
  }
  return random;
}

/*
 * Test iterations. Make sure that:
 * excluded numbers are skipped 
 * numbers are equally distributed
 */
(function(min, max, except) {
  var iterations = 1000000;
  var i;
  var random;
  var results = {};
  for (i = 0; i < iterations; i++) {
    random = getRandomExcept(min, max, except);
    results[random] = (results[random] || 0) + 1;
  }
  for (random in results) {
    console.log("value: " + random + ", count: " + results[random] + ", percent: " + results[random] * 100 / iterations + "%");
  }
})(1, 5, [2, 4]);

百合的盛世恋 2024-11-05 04:29:19

这是没有递归且没有创建巨大数组的示例:

const getRandomWithExclude = (min, max, excludeArray) => {
  const randomNumber = Math.floor(Math.random() * (max - min + 1 - excludeArray.length)) + min;
  return randomNumber + excludeArray.sort((a, b) => a - b).reduce((acc, element) => { return randomNumber >= element - acc ? acc + 1 : acc}, 0);
}

const min = 1;
const max = 10;
const excludeArray = [8,2,5];
const result = getRandomWithExclude(min, max, excludeArray);

This is example without recursion and without creating a huge array:

const getRandomWithExclude = (min, max, excludeArray) => {
  const randomNumber = Math.floor(Math.random() * (max - min + 1 - excludeArray.length)) + min;
  return randomNumber + excludeArray.sort((a, b) => a - b).reduce((acc, element) => { return randomNumber >= element - acc ? acc + 1 : acc}, 0);
}

const min = 1;
const max = 10;
const excludeArray = [8,2,5];
const result = getRandomWithExclude(min, max, excludeArray);
指尖凝香 2024-11-05 04:29:19

嗯:-?从数组中随机获取项目并确保它们都是唯一的最快方法是:

var array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];

Array.prototype.shuffle = function shuffle(){
    var tempSlot;
    var randomNumber;
    for(var i =0; i != this.length; i++){
        randomNumber = Math.floor(Math.random() * this.length);
        tempSlot = this[i]; 
        this[i] = this[randomNumber]; 
        this[randomNumber] = tempSlot;
    }
}

while(array.length!=0){
    array.shuffle();
    alert(array.pop());    
}

Hmz :-? Fastest way to randomly get items from an array and ensure they're all unique would be:

var array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];

Array.prototype.shuffle = function shuffle(){
    var tempSlot;
    var randomNumber;
    for(var i =0; i != this.length; i++){
        randomNumber = Math.floor(Math.random() * this.length);
        tempSlot = this[i]; 
        this[i] = this[randomNumber]; 
        this[randomNumber] = tempSlot;
    }
}

while(array.length!=0){
    array.shuffle();
    alert(array.pop());    
}
筱果果 2024-11-05 04:29:19

刚刚发现自己处于一种情况,我需要为每个游戏坐标生成一个很长范围内的随机数,但不包括一些已经采用的坐标。

正如您可以想象的那样,重新计算发生在帧之间(理想情况下在 10-14 毫秒内),因此使用递归、while 循环或生成极长数组甚至不是一个选项。

感谢 Salman A 和 Sebastian Umiński 展示了另一种更高效的解决问题的方法。

这是我修改后的 ES6 函数,我希望它能对我遇到的情况有所帮助:)

const randNum = (min, max, exclude = []) => {
  let num = Math.floor(Math.random() * (max - min + 1 - exclude.length) + min);
  exclude
    .slice()
    .sort((a, b) => a - b)
    .every((exeption) => exeption <= num && (num++, true));
  return num;
};

console.log(randNum(0, 24, [8]));

Just found myself in a situation where I needed to generate a random number in a really long range, for each game coordinate, BUT excluding some coordinates that are already taken.

As you can imagine recalculation happens between frames (within 10-14ms ideally), so using recursion, while-loop or generating extremely long array are not even an options.

Thanks Salman A and Sebastian Umiński for showing another more performant way of solving the problem.

So here's my revised ES6 function and I hope it helps somebody in a situation that I found myself in :)

const randNum = (min, max, exclude = []) => {
  let num = Math.floor(Math.random() * (max - min + 1 - exclude.length) + min);
  exclude
    .slice()
    .sort((a, b) => a - b)
    .every((exeption) => exeption <= num && (num++, true));
  return num;
};

console.log(randNum(0, 24, [8]));

指尖上的星空 2024-11-05 04:29:19

我确信有几种方法可以做到这一点,但是您可以将所有数字放入类似堆栈之类的东西中,将其全部打乱,然后从其中弹出以获取随机数。或者,每次随机查找它并将其从堆栈中删除。

I'm sure there are a few ways to do this, but you could put all the numbers into something like a stack, jumble it all up and then pop off of it to get your random numbers. Or, randomly seek into it every time and remove it from the stack.

一袭水袖舞倾城 2024-11-05 04:29:19

步骤1&GT;创建一个阵列check_array填充数组中的值,该值超出了随机数的范围(如果要在0-25中生成数字,则用26填充26)

step2-&gt;生成一个随机数并将其添加到Random_Array,并将其添加到check_array
那就是

i=0;
CHECK_ARRAY[i]=random;
i++;

step3-&gt;生成一个新的随机数,然后走过check_array,如果您发现26,则忽略,否则,如果您发现重复,然后重新生成一个随机数,然后再次继续步骤3,直到找到一个唯一的随机数!

step 1> create an array CHECK_ARRAY fill the array with value which is out of the range of your random number [fill it with 26 if you want to generate number within 0-25]

step2-> generate a random number and add it to RANDOM_ARRAY and also add it to the CHECK_ARRAY
that is

i=0;
CHECK_ARRAY[i]=random;
i++;

step3-> generate a new random number and go though the CHECK_ARRAY, if you found 26 then ignore, else if you found duplicate then re-generate a random number and continue step 3 again until you found an unique random number !

葬シ愛 2024-11-05 04:29:19

这是一个经过测试且简单的解决方案:

var array= [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];
var random_value; 
var index;
var shuffled_array = new Array(24);

for (var i = 0; i < 24; i++) { 
random_value = array[Math.floor(Math.random()*array.length)]; //Returns a value between 1 and 24
index = array.indexOf(random_card); //Gets the index of the choosen random value
array.splice(index, 1); //Go to index of that array and remove it
shuffled_array [i] = random_value; //Put that value in a new array

window.alert("array: "+array+"\n"+"random_value: "+random_value+"\n"+"shuffled_array: "+shuffled_array);
}

在其他解决方案中,我相信他们忘了搜索索引。

Here is a tested and simple solution:

var array= [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];
var random_value; 
var index;
var shuffled_array = new Array(24);

for (var i = 0; i < 24; i++) { 
random_value = array[Math.floor(Math.random()*array.length)]; //Returns a value between 1 and 24
index = array.indexOf(random_card); //Gets the index of the choosen random value
array.splice(index, 1); //Go to index of that array and remove it
shuffled_array [i] = random_value; //Put that value in a new array

window.alert("array: "+array+"\n"+"random_value: "+random_value+"\n"+"shuffled_array: "+shuffled_array);
}

In other solutions i believe they forgot to search for the index.

红衣飘飘貌似仙 2024-11-05 04:29:19

@alex chebotarsky

经过一些单元测试后,我发现一些额外的检查是审慎的:

/**
 * Generates a random int within the max and min range.
 * Maximum is exclusive and minimum is inclusive.
 * @param min
 * @param max
 */
export const randomInt = (
  min: number,
  max: number,
): number => (Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min)) + Math.ceil(min)));

/**
 * Generates a random int within the max and min range with an array of excludes.
 * Maximum is exclusive and minimum is inclusive.
 * @param min
 * @param max
 * @param excludes
 */
export const randomIntWithExclude = (
  min: number,
  max: number,
  excludes: number[] = [],
): number => {
  if (min === max && excludes.includes(min)) throw new RangeError('All values are excluded');
  if (min === max) return min;
  if (max < min) [max, min] = [min, max];

  let num = randomInt(min, max);
  if (!excludes || !excludes.length) return num;

  excludes
    .sort((a, b) => a - b)
    .every((except) => except <= num && (num >= max ? num -= 1 : num += 1, true));
  if (excludes.includes(num)) throw new RangeError('All values are excluded');
  return num;
};

如果您有兴趣,则进行单位测试:

import {
  genRndNumUniqArray,
  randomIntWithExclude,
  randomInt,
} from './mathFuncs';

describe('[NumberFuncs]', () => {
  test.repeats(
    { times: 1000 },
    '[randomIntWithExclude] Should generate a random number excluding values in an array',
    () => {
      const excludesLength = randomInt(0, 10);
      const excludes = excludesLength
        ? genRndNumUniqArray(0, 100, excludesLength)
        : [];

      const [min, max] = excludes.length
        ? [Math.min(...excludes), Math.max(...excludes)]
        : [randomInt(0, 10), randomInt(10, 100)];

      try {
        const num = randomIntWithExclude(min, max, excludes);
        expect(num).not.toBeIncludedIn(excludes);
        expect(num).toBeGreaterThanOrEqual(min);
        expect(num).toBeLessThan(max);
      } catch (error) {
        if (min === max && excludes.includes(min)) {
          expect(error).toBeInstanceOf(RangeError);
        }
      }
    },
  );

  test.repeats(
    { times: 100 },
    '[randomIntWithExclude] Should throw a `RangeError` if all possible values are in the excludes array',
    () => {
      const excludes = [...Array(randomInt(2, 10)).keys()];
      const [min, max] = [Math.min(...excludes), Math.max(...excludes)];

      try {
        randomIntWithExclude(min, max, excludes);
        expect(true).toBe(false); // This is not supposed to be reached since the code above throws an error
      } catch (error) {
        if (min === max && excludes.includes(min)) {
          expect(error).toBeInstanceOf(RangeError);
        }
      }
    },
  );
});

此功能是单位测试的依赖性:

/**
 * Generates an array of unique numbers
 * @param min
 * @param max
 * @param size
 */
export function genRndNumUniqArray(min: number, max: number, size: number): number[] {
  const rng = Math.min(max - min, size);
  if (rng < 1) return [];
  const nums = new Set<number>();
  while (nums.size !== rng) {
    const n = randomInt(min, max);
    nums.add(n);
  }
  return Array.from(nums);
}

如果您对 test.test.repeats.repeats ,它是一个自定义 jest 扩展:

./jest.extends.ts

const handleError = ({
  name,
  errors,
  failPct,
  canFailPct,
  passIfOnePasses,
  debug,
  times,
  passes,
}: {
  name: string,
  times: number,
  canFailPct: number,
  passIfOnePasses?: boolean,
  passes: number[]
  errors: [number, any][],
  failPct: number,
  debug?: boolean,
}) => {
  if (passIfOnePasses && passes.length) return;

  if (errors.length && failPct > (canFailPct ?? 0)) {
    if (debug) {
      throw new Error(`
Test: ${name}
Ran: ${times} times
Failures: \x1b[31m${errors.length}\x1b[0m
Passes: \x1b[32m${passes.length}\x1b[0m
Fail rate: \x1b[31m${failPct * 100}%\x1b[0m
${canFailPct ? `Failed more than the ${canFailPct * 100}% limit` : ''}\n
Errors:
${errors.map((e) => `RUN: ${e[0]}\n${e[1].message}`).join('\n\n')}
`);
    } else {
      throw new Error(`
Test: ${name}
Ran: ${times} times
Failures: \x1b[31m${errors.length}\x1b[0m
Passes: \x1b[32m${passes.length}\x1b[0m
Fail rate: \x1b[31m${failPct * 100}%\x1b[0m
${canFailPct ? `Failed more than the ${canFailPct * 100}% limit` : ''}\n
Last error:
${errors[errors.length - 1][1]}\n
You can pass the \x1b[1;33m\`debug: true\`\x1b[0m option to see all errors.
`);
    }
  }
};

const repeatTest = async (
  options: jest.RepeatWithCanFail | jest.RepeatWithPass | jest.RepeatWithDefaults,
  name: string,
  fn?: jest.ProvidesCallback,
  timeout?: number,
) => {
  if (options.canFailPct && (options.canFailPct < 0 || options.canFailPct > 1)) {
    throw new Error('`canFailPct` must be between 0 and 1');
  }

  const passes: number[] = [];
  const errors: [number, any][] = [];

  return test(name, async () => {
    for await (const i of [...Array(options.times).keys()]) {
      try {
        if (fn) {
          // @ts-ignore
          await fn();
          passes.push(i);
        }
      } catch (error) {
        errors.push([i, error.stack ?? error.toString()]);
      }
    }
    const failPct = errors.length / options.times;

    handleError({
      name,
      errors,
      failPct,
      canFailPct: options.canFailPct ?? 0,
      passIfOnePasses: options.passIfOnePasses,
      debug: options.debug,
      times: options.times,
      passes,
    });
  }, timeout);
};

test.repeats = repeatTest;
it.repeats = repeatTest;

它在失败的测试中打印此内容:

  [NumberFuncs]
    ✕ [getRandomIntWithExclude] (216 ms)

  ● [NumberFuncs] › [randomIntWithExclude]


    Test: [randomIntWithExclude]
    Ran: 1000 times
    Failures: 95
    Passes: 905
    Fail rate: 9.5%


    Last error:
    Error: expect(received).toBeGreaterThanOrEqual(expected)

    Expected: >= 67
    Received:    66

./jest.config.js< /code>

在测试前确保运行扩展文件,并在 jest.d.ts tsconfig.json 中包括Jest自定义类型,如果使用TypeScript。

/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  ...
  setupFilesAfterEnv: ['./jest/extends.ts'],
  ...
};

Jest.d.ts

export {}
declare global {
  namespace jest {  
    type RepeatWithCanFail = {
      times: number,
      canFailPct: number,
      passIfOnePasses?: undefined,
      debug?: boolean,
    }
    
    type RepeatWithPass = {
      times: number,
      canFailPct?: undefined,
      passIfOnePasses: boolean,
      debug?: boolean,
    }
    
    type RepeatWithDefaults = {
      times: number,
      canFailPct?: undefined,
      passIfOnePasses?: undefined,
      debug?: boolean,
    }

    type RepeatOpts<O = any> =
    O extends RepeatWithCanFail
    ? RepeatWithCanFail
    : O extends RepeatWithPass
    ? RepeatWithPass
    : RepeatWithDefaults;

    interface It {
      repeats: <O extends RepeatOpts>(
        options: RepeatOpts<O>,
        name: string,
        fn?: jest.ProvidesCallback,
        timeout?: number,
      ) => void;
    }
  }
}

Adding up on the great answer by @Alex Chebotarsky.

After some unit testing I found that some additional checks are prudent:

/**
 * Generates a random int within the max and min range.
 * Maximum is exclusive and minimum is inclusive.
 * @param min
 * @param max
 */
export const randomInt = (
  min: number,
  max: number,
): number => (Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min)) + Math.ceil(min)));

/**
 * Generates a random int within the max and min range with an array of excludes.
 * Maximum is exclusive and minimum is inclusive.
 * @param min
 * @param max
 * @param excludes
 */
export const randomIntWithExclude = (
  min: number,
  max: number,
  excludes: number[] = [],
): number => {
  if (min === max && excludes.includes(min)) throw new RangeError('All values are excluded');
  if (min === max) return min;
  if (max < min) [max, min] = [min, max];

  let num = randomInt(min, max);
  if (!excludes || !excludes.length) return num;

  excludes
    .sort((a, b) => a - b)
    .every((except) => except <= num && (num >= max ? num -= 1 : num += 1, true));
  if (excludes.includes(num)) throw new RangeError('All values are excluded');
  return num;
};

If you are interested, here goes the unit test:

import {
  genRndNumUniqArray,
  randomIntWithExclude,
  randomInt,
} from './mathFuncs';

describe('[NumberFuncs]', () => {
  test.repeats(
    { times: 1000 },
    '[randomIntWithExclude] Should generate a random number excluding values in an array',
    () => {
      const excludesLength = randomInt(0, 10);
      const excludes = excludesLength
        ? genRndNumUniqArray(0, 100, excludesLength)
        : [];

      const [min, max] = excludes.length
        ? [Math.min(...excludes), Math.max(...excludes)]
        : [randomInt(0, 10), randomInt(10, 100)];

      try {
        const num = randomIntWithExclude(min, max, excludes);
        expect(num).not.toBeIncludedIn(excludes);
        expect(num).toBeGreaterThanOrEqual(min);
        expect(num).toBeLessThan(max);
      } catch (error) {
        if (min === max && excludes.includes(min)) {
          expect(error).toBeInstanceOf(RangeError);
        }
      }
    },
  );

  test.repeats(
    { times: 100 },
    '[randomIntWithExclude] Should throw a `RangeError` if all possible values are in the excludes array',
    () => {
      const excludes = [...Array(randomInt(2, 10)).keys()];
      const [min, max] = [Math.min(...excludes), Math.max(...excludes)];

      try {
        randomIntWithExclude(min, max, excludes);
        expect(true).toBe(false); // This is not supposed to be reached since the code above throws an error
      } catch (error) {
        if (min === max && excludes.includes(min)) {
          expect(error).toBeInstanceOf(RangeError);
        }
      }
    },
  );
});

This function is a dependency for the unit test:

/**
 * Generates an array of unique numbers
 * @param min
 * @param max
 * @param size
 */
export function genRndNumUniqArray(min: number, max: number, size: number): number[] {
  const rng = Math.min(max - min, size);
  if (rng < 1) return [];
  const nums = new Set<number>();
  while (nums.size !== rng) {
    const n = randomInt(min, max);
    nums.add(n);
  }
  return Array.from(nums);
}

And if you are even more interested about the test.repeats, it is a custom jest extension:

./jest.extends.ts

const handleError = ({
  name,
  errors,
  failPct,
  canFailPct,
  passIfOnePasses,
  debug,
  times,
  passes,
}: {
  name: string,
  times: number,
  canFailPct: number,
  passIfOnePasses?: boolean,
  passes: number[]
  errors: [number, any][],
  failPct: number,
  debug?: boolean,
}) => {
  if (passIfOnePasses && passes.length) return;

  if (errors.length && failPct > (canFailPct ?? 0)) {
    if (debug) {
      throw new Error(`
Test: ${name}
Ran: ${times} times
Failures: \x1b[31m${errors.length}\x1b[0m
Passes: \x1b[32m${passes.length}\x1b[0m
Fail rate: \x1b[31m${failPct * 100}%\x1b[0m
${canFailPct ? `Failed more than the ${canFailPct * 100}% limit` : ''}\n
Errors:
${errors.map((e) => `RUN: ${e[0]}\n${e[1].message}`).join('\n\n')}
`);
    } else {
      throw new Error(`
Test: ${name}
Ran: ${times} times
Failures: \x1b[31m${errors.length}\x1b[0m
Passes: \x1b[32m${passes.length}\x1b[0m
Fail rate: \x1b[31m${failPct * 100}%\x1b[0m
${canFailPct ? `Failed more than the ${canFailPct * 100}% limit` : ''}\n
Last error:
${errors[errors.length - 1][1]}\n
You can pass the \x1b[1;33m\`debug: true\`\x1b[0m option to see all errors.
`);
    }
  }
};

const repeatTest = async (
  options: jest.RepeatWithCanFail | jest.RepeatWithPass | jest.RepeatWithDefaults,
  name: string,
  fn?: jest.ProvidesCallback,
  timeout?: number,
) => {
  if (options.canFailPct && (options.canFailPct < 0 || options.canFailPct > 1)) {
    throw new Error('`canFailPct` must be between 0 and 1');
  }

  const passes: number[] = [];
  const errors: [number, any][] = [];

  return test(name, async () => {
    for await (const i of [...Array(options.times).keys()]) {
      try {
        if (fn) {
          // @ts-ignore
          await fn();
          passes.push(i);
        }
      } catch (error) {
        errors.push([i, error.stack ?? error.toString()]);
      }
    }
    const failPct = errors.length / options.times;

    handleError({
      name,
      errors,
      failPct,
      canFailPct: options.canFailPct ?? 0,
      passIfOnePasses: options.passIfOnePasses,
      debug: options.debug,
      times: options.times,
      passes,
    });
  }, timeout);
};

test.repeats = repeatTest;
it.repeats = repeatTest;

It prints this on failing tests:

  [NumberFuncs]
    ✕ [getRandomIntWithExclude] (216 ms)

  ● [NumberFuncs] › [randomIntWithExclude]


    Test: [randomIntWithExclude]
    Ran: 1000 times
    Failures: 95
    Passes: 905
    Fail rate: 9.5%


    Last error:
    Error: expect(received).toBeGreaterThanOrEqual(expected)

    Expected: >= 67
    Received:    66

./jest.config.js

Make sure to run the extension file before tests and include the jest custom types in jest.d.ts and tsconfig.json if using typescript.

/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  ...
  setupFilesAfterEnv: ['./jest/extends.ts'],
  ...
};

jest.d.ts

export {}
declare global {
  namespace jest {  
    type RepeatWithCanFail = {
      times: number,
      canFailPct: number,
      passIfOnePasses?: undefined,
      debug?: boolean,
    }
    
    type RepeatWithPass = {
      times: number,
      canFailPct?: undefined,
      passIfOnePasses: boolean,
      debug?: boolean,
    }
    
    type RepeatWithDefaults = {
      times: number,
      canFailPct?: undefined,
      passIfOnePasses?: undefined,
      debug?: boolean,
    }

    type RepeatOpts<O = any> =
    O extends RepeatWithCanFail
    ? RepeatWithCanFail
    : O extends RepeatWithPass
    ? RepeatWithPass
    : RepeatWithDefaults;

    interface It {
      repeats: <O extends RepeatOpts>(
        options: RepeatOpts<O>,
        name: string,
        fn?: jest.ProvidesCallback,
        timeout?: number,
      ) => void;
    }
  }
}
巷雨优美回忆 2024-11-05 04:29:19
<div id="number" style="color: red; margin-left: 200px;">array</div>
<div id="arr" style="color: red; margin-left: 200px;">length</div>
<script>
  var arrayOfIndexesToExclude = new Array();
  function getRandomWithManyExclusions(){
    var rand = null;
      
		  	do{
		    	rand = Math.round(Math.random() * ( 9));
		    	if(arrayOfIndexesToExclude.length >= 10){
		 			  arrayOfIndexesToExclude.length = 0;	   			
		    	}
				}while(arrayOfIndexesToExclude.includes(rand));
			
			arrayOfIndexesToExclude.push(rand);  
    
    document.getElementById("number").innerHTML = arrayOfIndexesToExclude;
    document.getElementById("arr").innerHTML = arrayOfIndexesToExclude.length;
  }
</script>

<div id="number" style="color: red; margin-left: 200px;">array</div>
<div id="arr" style="color: red; margin-left: 200px;">length</div>
<script>
  var arrayOfIndexesToExclude = new Array();
  function getRandomWithManyExclusions(){
    var rand = null;
      
		  	do{
		    	rand = Math.round(Math.random() * ( 9));
		    	if(arrayOfIndexesToExclude.length >= 10){
		 			  arrayOfIndexesToExclude.length = 0;	   			
		    	}
				}while(arrayOfIndexesToExclude.includes(rand));
			
			arrayOfIndexesToExclude.push(rand);  
    
    document.getElementById("number").innerHTML = arrayOfIndexesToExclude;
    document.getElementById("arr").innerHTML = arrayOfIndexesToExclude.length;
  }
</script>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文