🛠 随机挑选工具类
该库从@mxssfd/ts-utils迁移而来
npm install @tool-pack/random-picker随机选取单个/多个选项
从选项池中随机选中一个
import { RandomPicker } from '@mxssfd/random-picker';
const picker = new RandomPicker([1, 2]);
// pick 1个
const v1 = picker.pick(); // 随机从1,2中选中一个,每个选项选中的几率是50%
// pick多个
const v2 = picker.pick(2); // 随机选中2个,可能会出现重复的
const v3 = picker.pick(10); // 随机选中10个,会一直在1,2之间选取从选项池中随机拿走一个
import { RandomPicker } from '@mxssfd/random-picker';
const picker = new RandomPicker([1, 2]);
// take 1个
const v1 = picker.take(); // 随机从1,2中选中一个,每个选项选中的几率是50%
// take多个
const v2 = picker.take(2); // 随机选中2个,不会出现重复的
const v3 = picker.take(10); // 随机选中10个,但是最多只会返回两个,多了的会去掉
const v4 = picker.take(); // null。因为在上面已经取走了所有选项
const v5 = picker.pick(); // null。pick和take共享一个选项池,take拿走了以后,pick也会被影响。
// 如果需要再次pick或者take的话需要reset
picker.reset();
// 此时又可以pick/take了
const v6 = picker.pick(); // 1 | 2
const v7 = picker.take(); //take 如果有相同的也只会拿走第一个
| take | pick | |
|---|---|---|
| 可以选取一个 | 是 | 是 | 
| 可以选取多个 | 是 | 是 | 
| 选取多个时是否可能会重复选取 | 否 | 是 | 
| 选取多个时是否可以超出可选数量 | 否 | 是 | 
| 影响 | pick | 无 | 
| 是否会从选项池中移除 | 是 | 否 | 
import { RandomPicker } from '@mxssfd/random-picker';
// 默认权重为1
const picker = new RandomPicker([1, 2]);
// 约等于
const picker2 = new RandomPicker([
  [1, 1], // [选项, 权重]
  [2, 1], // [选项, 权重]
]);选项有权重的概念
注:权重(weights)不完全等于选中几率。
规则:
- 如果总共有 3 个选项,且权重都为 1 的选项,那么每个选中几率是 33%。
- 如果总共只有 1 个选项,且唯一选项权重为 1,那么选中几率是 100%。
- 如果总共有 2 个选项,第一个选项权重为 20,第二个权重为 80,那么选中几率依次为 20%、80%。
- 添加权重为 0 或者为负数的选项会抛出异常
- 如果有三个权重为 1 的选项,take 一个选项会刷新选项池,剩余两个的选中几率各为 50%,再次 take 一个,则唯一的一个选中几率是 100%
- 每额外添加、删除、take 一个选项,选项池内选项的选中几率都会重新计算
import { RandomPicker } from '@mxssfd/random-picker';
const picker = new RandomPicker([1, 2]);
// 添加单个选项
picker.option(3); // 添加权重为1的选项3
picker.option(4, 2); // 添加选项3,并且设置权重为2
// 添加动态权重的选项33,该权重回调函数会在计算所有静态权重后调用,此时如果只有一个动态权重的话约等于50%
picker.option(33, (weights) => weights);
// 添加多个选项
picker.options([5, 6]); // 添加权重为1的选项5和6
picker.options([
  [7, 2],
  [8, 2],
]); // 添加权重为2的选项7和8
picker.options([[9], [10, (weights) => weight]]); // 添加权重为1的选项9和动态权限的选项10,此时的选项10和选项33选中几率为33.3333%,其他按权限计算各个选项选中几率注意:如果添加选项前有用过 **(v0.0.2 已弃用该特性,新特性为可随意添加删除)**take 的话,调用 option/options 会自动刷新选项池,已经 take 过的选项会重新进入选项池
import { RandomPicker } from '@mxssfd/random-picker';
const picker = new RandomPicker([1]);
console.log(picker.rateOf(1)); // 选中几率 100
picker.option(2);
console.log(picker.rateOf(1)); // 选中几率 50
console.log(picker.rateOf(2)); // 选中几率 50
picker.option(3, 2);
console.log(picker.rateOf(1)); // 选中几率 25
console.log(picker.rateOf(2)); // 选中几率 25
console.log(picker.rateOf(3)); // 选中几率 50
picker.option(4, (weightTotal) => weightTotal);
console.log(picker.rateOf(1)); // 选中几率 12.5
console.log(picker.rateOf(2)); // 选中几率 12.5
console.log(picker.rateOf(3)); // 选中几率 25
console.log(picker.rateOf(4)); // 选中几率 50
picker.option(5, (weightTotal) => weightTotal);
console.log(picker.rateOf(1)); // 选中几率 8.3333
console.log(picker.rateOf(2)); // 选中几率 8.3333
console.log(picker.rateOf(3)); // 选中几率 16.6667
console.log(picker.rateOf(4)); // 选中几率 33.3333
console.log(picker.rateOf(5)); // 选中几率 33.3334
// 重复选项
const picker2 = new RandomPicker([1, 1, 1]);
console.log(picker2.rateOf(1)); // 选中几率 100import { RandomPicker } from '@mxssfd/random-picker';
const picker = new RandomPicker([1, 2, 3]);
picker.remove(1); // 此时会把选项1移除
picker.options([1, 1, 1, 1, 1]); // 添加5个1
picker.remove(1); // 会把上面添加的5个1都删除掉注意:如果移除的选项不存在于 seed 或者未备份的话,将不能恢复
import { RandomPicker } from '@mxssfd/random-picker';
const seed = [1, 2, 3];
const picker = new RandomPicker(seed);
// 当take 3次后,选项池就为空了,此时再take或pick都会返回null
picker.take(seed.length); //  [number,number,number]
picker.take(); // invalid null
// 重置选项池
picker.reset();
picker.take(); // 1 | 2 | 3
// 注意:不会重置remove过的选项
picker.reset();
picker.remove(1); // 移除选项1
picker.take(seed.length); //  [number,number]
picker.reset();
picker.take(seed.length); //  重置回去也是[number,number]
// option
picker.reset();
picker.option(5);
picker.take(seed.length); //  [number,number,number]
// 使用reset可以重置option/options添加的选项
picker.reset();
picker.take(seed.length); //  [number,number,number]
//import { RandomPicker } from '@mxssfd/random-picker';
const seed = [1, 2, 3];
const picker = new RandomPicker(seed);
// 当take 3次后,选项池就为空了,此时再take或pick都会返回null
picker.take(seed.length); //  [number,number,number]
picker.take(); // invalid null
// 重置选项池
picker.resetWithSeed();
picker.take(); // 1 | 2 | 3
// 注意:会重置remove过的选项
picker.resetWithSeed();
picker.remove(1); // 移除选项1
picker.take(seed.length); //  [number,number]
picker.resetWithSeed();
picker.take(seed.length); //  [number,number,number]
// option
picker.resetWithSeed();
picker.option(5);
picker.take(seed.length); //  [1 | 2 | 3 | 5, ...]
// 使用resetWithSeed会丢弃option/options添加的选项
picker.resetWithSeed();
picker.take(seed.length); //  [1 | 2 | 3 , ...]| reset | resetWithSeed | |
|---|---|---|
| 恢复被 take的选项 | 是 | 是 | 
| 重置 option/options添加的选项 | 是 | 否,会丢弃选项 | 
| 恢复被 remove的选项 | 否 | 如果是存在于 seed的选项被remove会被恢复 | 
import { RandomPicker, Seed } from '@mxssfd/random-picker';
const seed: Seed<number> = [1, 2, [3, (weights: number) => weights]];
const picker = new RandomPicker(seed);
// export 导出所有选项
console.log(picker.export()); // [[1, 1], [2, 1], [3, (weights: number) => weights]]
// take过的的选项也会导出
picker.take(3);
console.log(picker.export()); // [[1, 1], [2, 1], [3, (weights: number) => weights]]
// 不会导出remove过的选项
picker.remove(3);
console.log(picker.export()); // [[1, 1], [2, 1]]import { RandomPicker, Seed } from '@mxssfd/random-picker';
const seed: Seed<number> = [1, 2, [3, (weights: number) => weights]];
const picker = new RandomPicker(seed);
// exportPool 导出剩余选项
console.log(picker.exportPool()); // [[1, 1], [2, 1], [3, (weights: number) => weights]]
// take过的选项不会导出
picker.take(3);
console.log(picker.exportPool()); // []
picker.reset();
// 不会导出remove过的选项
picker.remove(3);
console.log(picker.exportPool()); // [[1, 1], [2, 1]]| export(导出未remove的所有选项) | exportPool(剩余可供选择的所有选项) | |
|---|---|---|
| 能导出 take过的选项 | 是 | 否 | 
| 能导出 remove过的选项 | 是 | 否 | 
import { RandomPicker } from '@mxssfd/random-picker';
const picker = new RandomPicker([1, 2, [3, 98]]);
picker.take(); // 3  98%几率会出现3
console.log(picker.poolOptions); // [1|2, 1|2]
picker.take();
console.log(picker.poolOptions); // [1|2]
picker.take();
console.log(picker.poolOptions); // []import { RandomPicker } from '@mxssfd/random-picker';
const picker = new RandomPicker([1, 2, 3]);
// 初始都有3个
console.log(picker.len, picker.poolLen); // 3, 3
picker.take(2); // 拿走两个
console.log(picker.len, picker.poolLen); // 3, 1
picker.remove(3); // 移除3
console.log(picker.len, picker.poolLen); // 2, 2
picker.take(2); // 拿走两个
console.log(picker.len, picker.poolLen); // 2, 0如果你有选择困难症,那么全局安装此工具npm包可以帮助你选择,类似掷骰子
index.js
#!/usr/bin/env node
const { RandomPicker } = require('../dist/random-picker.cjs');
/**
 * @returns {
 * {
 * help?:boolean;h?:boolean;
 * pick?:boolean|number;p?:boolean|number;
 * take?:boolean|number;t?:boolean;
 * options:(number | string| [string|number,number])[]
 * }
 * }
 */
function getOptions() {
  const args = process.argv.slice(2);
  const keyReg = /^-+/;
  return args.reduce((result, cur, index) => {
    const next = args[index + 1];
    if (keyReg.test(cur)) {
      const key = cur.replace(keyReg, '');
      // --take 2 获取take后面的2
      result[key] = /^\d+$/.test(next) ? Number(next) ?? true : true;
    } else if (/^\[.+]$/.test(cur)) {
      // 随机选项
      try {
        result.options = JSON.parse(cur);
      } catch (e) {
        throw new TypeError('请检查选项数组是否符合JSON格式');
      }
    }
    return result;
  }, {});
}
function showHelp() {
  const helpContent = `
--help/-h 帮助
--take/-t 使用take获取随机选项  获取2个随机选项--take 2 '[1,2]'
--pick/-p 使用pick获取随机选项 同take
`.trim();
  console.log(helpContent);
}
/**
 *
 * @param {(string|number)[]} options
 * @param {boolean|number} take
 * @param {boolean|number} pick
 */
function runRP(options, take, pick) {
  const rm = new RandomPicker(options);
  const result = take
    ? rm.take(typeof take === 'number' ? take : 1)
    : rm.pick(typeof pick === 'number' ? pick : 1);
  console.log(result);
}
function setup() {
  const options = getOptions();
  if (options.h || options.help) {
    showHelp();
    return;
  }
  if (!Array.isArray(options.options)) {
    throw new Error('缺少选项');
  }
  runRP(options.options, options.take ?? options.t, options.pick ?? options.p);
}
setup();需要注意的是文件头部的#!/usr/bin/env node不能缺少,否则命令行工具是运行不了的。
package.json
+ "bin": {
+   "random-picker": "./bin/index.js"
+ },在控制台打开项目目录,然后输入命令npm link,这样random-picker包就相当于全局安装好了。
比如说我明天不知道学习好还是躺平好,那么我就可以使用它来帮助我选择,命令行输入如下:
random-picker '[["睡觉", 99], "学习"]'
那么 99%的几率是睡觉 😂
以上是实现步骤,你也可以直接安装npm i @mxssfd/random-picker -g使用
--------------------------|---------|----------|---------|---------|----------------------------------------------------------------------------------------
File                      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------------|---------|----------|---------|---------|----------------------------------------------------------------------------------------
 random-picker/src        |     100 |      100 |     100 |     100 |
  OptionsPool.ts          |     100 |      100 |     100 |     100 |
  OptionsStore.ts         |     100 |      100 |     100 |     100 |
  RandomPicker.ts         |     100 |      100 |     100 |     100 |
--------------------------|---------|----------|---------|---------|----------------------------------------------------------------------------------------
测试覆盖率达到 100%请放心食用