用 js 快速生成一篇 《千人围攻》

319 阅读4分钟

在线体验: cnname Demo - StackBlitz

前言

不记得是哪一次在公司摸鱼的时候,刷到了这样一篇"小说":

image.png

我惊呆了。

也就是在这一天我下定决心,为了中华文学领域的革新,我也要写一篇这样的小说,将这种写法发扬光大。

动笔

我打开了尘封已久的 VSCode,创建了新的文件夹,我想,能快速写出这样文章的关键点在于,能快速生成很多中文名字,于是我给文件夹命名为 cnname。cn(中国人的)name(名字)。

我努力的整理着自己的思路,首先,我需要一份姓氏和取名常用字的数据字典。我觉得这种苦力事情应该交给 AI 牛马来做,于是我命令 chatGPT 给我分别生成一份取名常用字的字典。

image.png

轻松的拿到了。

接着,我开始编写 js 的部分,大体的思路为:

  1. 随机从字典中抽取姓氏
  2. 随机确定名字长度(1 或 2)
  3. 根据长度随机抽取常用字作为名
  4. 根据需要生成的名字数量重复以上步骤

于是一个很简单的批量生成名字的方法雏形就完成了。

function getName(num) {
  for (let i = 0; i < num; i++) {
    const surname = pickRandomSurname();
    const nameLength = Math.random() > 0.5 ? 2 : 1;
    const givenName = pickRandomWords(nameLength);
    const fullname = surname + givenName;
    result.push(fullname);
  }
}

此时用的随机抽取的方法就是根据数组的长度随机一个数组索引并取值:

function randomNumber(a: number, b: number): number {
  return Math.floor(Math.random() * (b - a + 1));
}

function pickRandomEle(array: string[], n: number): string[] {
  if (!array.length || n <= 0) return [];

  const result = [];
  const length = array.length;
  while (result.length < n) {
    const i = randomNumber(0, length - 1);
    result.push(array[i]);
  }
  return result;
}

方法差不多了,写一个小说的 md 模板:

const 主角 = getName();
const 反派 = getName();

const novel = `# 第 1 章 千人围攻

"${主角},你已经被我们围攻了!!!"

${反派}的厉啸从身后传来,随着尖锐的破空声,一声利箭没入${主角}身旁的黄土墙。

${主角}心头微凉,定睛一看。

只见阴暗的密林四处,已经隐隐约约钻出千道黑色人影。

他们分别是:

${getName(1000).join(',')}。
`;

const __filename = fileURLToPath(import.meta.url);

const __dirname = path.dirname(__filename);

const resolvePath = path.resolve(__dirname, filePath);

writeFileSync(resolvePath, content, 'utf-8');

现在调用一下试试。getName(1000) 走起。

image.png

有了!

不满

现在是能生成了,但里面的名字大多都是稀有姓氏,看着怪怪的,我想现实生活中常见的姓氏应该多点,故作了些许调整:

  1. 默认只用常见姓氏,(人口排名前 150)
  2. 修改随机抽取的方式,改为姓氏排名越靠前,则抽中的概率越大(理应遍地是老王才对...)

新的抽取方式,添加权重:

export function pickWeightEle(array: string[], n: number): string[] {
  if (!array.length || n <= 0) return [];

  const len = array.length;
  const totalWeight = (len * (len + 1)) / 2;
  const result: string[] = [];

  while (result.length < n) {
    let random = Math.random() * totalWeight;
    let index = 0;
    let weight = len;

    while (random >= weight) {
      random -= weight;
      index++;
      weight--;
    }

    result.push(array[index]);
  }

  return result;
}
权重机制

“倒三角权重” 模拟抽取:数组越靠前的元素,权重越大,越容易被选中。 比如数组长度为 4:

  • 第 0 个元素权重为 4
  • 第 1 个元素权重为 3
  • 第 2 个元素权重为 2
  • 第 3 个元素权重为 1

总权重是 (4 * (4 + 1)) / 2 = 10,这是求从 1 到 4 的和的公式。

抽取流程

每次抽取时:

  1. 随机生成一个 0 到 totalWeight 之间的数。
  2. 依次减去权重,直到这个随机数小于某个元素的权重,那就是命中的元素。
  3. 把命中的元素加到结果中。
  4. 直到取够 n 个元素。

再试试。getName(1000) 走起。

image.png

漂亮,这回生成的人名自然多了。现在我也可以写小说了!

修饰

现在再给这个项目添加一些小小的细节:

  • 常用字分类以支持五行、性别等

image.png

  • 添加复姓,固定姓氏,固定名字,叠名,固定长度,是否唯一等支持
  • 添加工具方法,包括是否为姓氏,解析名字(返回姓和名)等
  • 添加测试用例 Vitest (Vitest | Next Generation testing framework)

image.png

image.png

尴尬

领导发现了我在写这篇文章,并暗示我工作完成了吗。

尾声

我将这个项目发到了 npm 上,如果你也愿意为了中华文坛出一份力,不妨下载来试试。

github: yyz945947732/cnname: 🐼 Randomly generate Chinese names!

在线体验: cnname Demo - StackBlitz

这是我做过可能最没用也最无聊的项目,但也是我本人开发的项目中最喜欢的项目,在开发这个项目的过程中自己也慢慢找回了编程的乐趣。希望你也能依然喜欢编程。