Skip to content

js-tool-pack/random-picker

Repository files navigation

@tool-pack/random-picker

🛠 随机挑选工具类

该库从@mxssfd/ts-utils迁移而来

安装方法

npm install @tool-pack/random-picker

使用方法

pick / take

随机选取单个/多个选项

pick

从选项池中随机选中一个

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之间选取

take

从选项池中随机拿走一个

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
是否会从选项池中移除

权重(weights)

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 一个选项,选项池内选项的选中几率都会重新计算

option/options 添加选项

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%,其他按权限计算各个选项选中几率

注意:如果添加选项前有用过 take 的话,调用 option/options 会自动刷新选项池,已经 take 过的选项会重新进入选项池**(v0.0.2 已弃用该特性,新特性为可随意添加删除)**

rateOf 获取选项的选中几率

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)); // 选中几率 100

remove 移除选项

import { 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 或者未备份的话,将不能恢复

reset/resetWithSeed 重置选项池

使用 reset重置选项池

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]

//

使用 resetWithSeed重置选项池

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会被恢复

export / exportPool 导出选项

export导出所有选项

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]]

export导出剩余选项

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 过的选项

poolOptions 查看剩余选项

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); // []

len / poolLen 查看选项数量

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包可以帮助你选择,类似掷骰子

添加命令行脚本步骤

1.在项目根目录下新建bin目录,然后在bin目录下新建index.js文件

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不能缺少,否则命令行工具是运行不了的。

2.然后修改package.json加上bin属性

package.json

+ "bin": {
+   "random-picker": "./bin/index.js"
+ },

3.调试

在控制台打开项目目录,然后输入命令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%请放心食用