前言
在上节课的内容中我们学习了node.js的基础理论知识,今天也是话不多说,我们直接实操一个小功能------文章生成器,来熟悉使用node的语法和模块
初始化结构
-
使用npm init -y 初始化生成文件,在生成的package.json文件中添加
"type": "module"
,使得我们能够使用ESM和Common.js语法 -
新建curpus文件夹,在里面新建data.json文件作为我们的语料库。数据已经准备好。
- 新建lib文件,便于我们封装一些函数。新建output,便于观察我们的输出结果。添加入口文件index.js
实战
读取数据
在index.js中可以通过 readFileSync()
读取到data中的数据。
js
import fs from 'fs';
const content = fs.readFileSync('./curpus/data.json', 'utf8');
console.log(content);
考虑到项目压缩时可能带来的路径改变,ESM提供了 fileURLToPath 来获取文件所在的绝对路径。
绝对路径
当将项目部署在服务器或在开发过程中需要在其他开发者电脑中访问文件时,绝对路径显得尤其重要。如何访问当前文件的绝对路径?
- 从
url
中引入fileURLToPath
可以将 file:
URL 转换为文件系统的路径。在ESM中,可以通过 import.meta.url
访问当前模块的URL,。然后使用 fileURLToPath
将这个URL转换为文件系统的路径
- 从
path
中引入dirname,resolve
dirname 可以访问当前文件所在的文件夹,再通过resolve拼接你想访问的文件可以得到完整的绝对路径。
优化改进后的代码如下:
js
import fs from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
const url = import.meta.url;
const path = resolve(dirname(fileURLToPath(url)), 'curpus/data.json')
const data = fs.readFileSync(path, 'utf8');
console.log(path);
在这里我们可以通过 ||JSON.parse(data)||来将数据转换为对象。
将数据转换为对象:
实现两个功能
为了生成文章我们需要封装两个函数实现两个功能
随机选取内容的模块 random.js
1. 生成随机整数
Math.random() * (max - min + 1)
生成一个 [0,max - min + 1
) 之间的随机数(因为Math.random()
生成的是 [0, 1) 的数,乘以max - min + 1
后范围变为 [0,max - min + 1
))。- 通过
Math.floor()
向下取整,我们得到一个在 [0,max - min
] 范围内的整数。 - 最后,通过加上
min
,我们将范围调整到 [min
,max
]。
js
export function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
2. 确保选择的元素不重复
从某个数组 arr
中随机选择一个元素,确保这个元素不会与上一次选择的元素(lastPicked
)相同。
这段代码定义了一个名为 createRandomPicker
的函数,它接受一个数组 arr
作为参数,并返回一个名为 randomPick
的函数。
randomPick函数的作用:
-
首先,计算数组的长度减一(
len
),因为数组索引是从 0 开始的,所以最后一个元素的索引是length - 1
。 -
然后,使用
randomInt
函数来生成一个介于 0 和len
之间的随机整数作为索引(index
)。 -
接下来,使用数组解构赋值将随机索引处的元素与最后一个元素交换位置。这样,每次调用
randomPick
时,都会将最后一个元素"移动到"随机索引处,并返回它。
js
export function createRandomPicker(arr) {
// 数组复制,使得更改数组不影响原数据
arr = [...arr]
// 实现随机选择逻辑
function randomPick() {
// 数组下标长度
const len = arr.length - 1
// 控制index的范围
const index = randomInt(0, len);
[arr[index], arr[len]] = [arr[len], arr[index]]
return arr[index]
}
randomPick() // 放弃第一次结果
return randomPick
}
重组语料库
1. 替换文本
这个函数 sentence
的作用是根据提供的两个参数来生成和修改文本句子。它首先调用一个函数(pick
)来获取一个初始的句子(或字符串),然后遍历一个替换器对象(replacer
),在该对象中定义了需要替换的占位符( {{key}}
)以及它们应该被替换成的内容或生成内容的函数。
函数的工作流程如下:
-
获取初始句子 :通过调用
pick()
函数获取一个初始的句子(或字符串),这个函数预期会返回一个包含占位符(如{{key}}
)的字符串。 -
遍历替换器对象 :
replacer
是一个对象,其属性名对应要替换的占位符中的key
(去掉{{
和}}
),属性值则是替换后的内容或一个函数,该函数在被调用时返回替换内容。 -
执行替换 :对于
replacer
中的每一对键值,函数使用正则表达式来查找初始句子中所有匹配的占位符({{key}}
),并将它们替换为对应的值或调用对应函数后返回的值。这里使用了g
标志来确保所有匹配的占位符都被替换,而不仅仅是第一个。 -
返回修改后的句子:完成所有替换后,函数返回修改后的句子。
js
// 引入随机生成的句子
import { randomInt, createRandomPicker } from './random.js'
// 得到一个句子
function sentence(pick, replacer) {
let ret = pick()
// 遍历从语料库中得到的句子,循环遍历
for (const key in replacer) {
// 替换数据
ret = ret.replace(
// 利用正则表达式,使用'g'占位符替换key
new RegExp(`{{${key}}}`, 'g'),
// 判断是不是函数
typeof replacer[key] === 'function' ? replacer[key]() : replacer[key]
)
}
return ret
}
2.组合文本,生成文章
定义了一个名为 generate
的函数,旨在根据提供的标题和语料库(corpus
)生成一篇文章。文章由多个段落组成,每个段落的内容根据一定的规则和概率从语料库中随机选择并组合。
函数内部逻辑:
-
文章长度 :首先,使用
randomInt
函数(未在代码段中定义,但基于上下文,我们假设它生成一个指定范围内的随机整数)生成一个介于min
和max
之间的随机整数,作为目标文章长度(articleLenth
)。 -
语料库处理 :从
corpus
对象中提取五个数组,并使用createRandomPicker
函数(未在代码段中定义,但根据命名和上下文,我们假设它返回一个函数,该函数在每次调用时从给定数组中随机选择一个元素)为每个数组创建一个随机选择器(pickFamous
,pickBoshBefore
,pickBosh
,pickConclude
,pickSaid
)。 -
生成文章 :通过一个外层
while
循环,持续生成段落直到文章的总长度达到或超过目标长度(articleLenth
)。-
在内层循环中,首先生成一个段落的目标长度(
sectionLength
),范围在 100 到 300 个字符之间。 -
然后,通过另一个
while
循环,持续向段落中添加内容直到其长度达到或超过目标段落长度。内容的添加基于一个随机数n
,该随机数决定了从哪个语料库部分选择内容:- 如果
n
小于 20,使用sentence
函数(未在代码段中定义,但基于上下文,我们假设它接受一个生成器函数和一个替换器对象,并返回一个经过替换的字符串)从famous
部分选择内容,并可能使用said
和conclude
替换器。 - 如果
n
在 20 到 50 之间(不含50),先使用bosh_before
部分的内容(可能带有title
替换),然后添加bosh
部分的内容。 - 如果
n
大于或等于 50,只从bosh
部分选择内容。
- 如果
-
-
返回文章:当文章的总长度达到或超过目标长度时,函数返回包含所有段落的数组。
js
// 打造函数,可传入参数文章标题和字数多少
export function generate(title, { corpus, min = 500, max = 800 }) {
// 文章长短为随机数
const articleLenth = randomInt(min, max)
// 提取语料库(`corpus`)中的特定字段
const { famous, bosh_before, bosh, conclude, said } = corpus
// 为这些字段中的每一个创建随机选择器,方便从每个字段的集合中随机选择一个元素
const [pickFamous, pickBoshBefore, pickBosh, pickConclude, pickSaid]
= [famous, bosh_before, bosh, conclude, said].map(createRandomPicker)
// 定义文章空数组
const article = []
// 文章初始长度为0
let totalLength = 0
// 文章字数不够时不停生成
while (totalLength < articleLenth) {
// 段落为空字符串
let section = ''
// 段落长度随机100到300字之间
const sectionLength = randomInt(100, 300)
// 不达标循环生成
while (section.length < sectionLength) {
// 假设有随机数0到100
const n = randomInt(0, 100)
// 段落构成随机组合
if (n < 20) {
section += sentence(pickFamous,
{ said: pickSaid, conclude: pickConclude })
} else if (n < 50) {
section += sentence(pickBoshBefore,
{ title }) + sentence(pickBosh, { title })
} else {
section += sentence(pickBosh, { title })
}
}
// 总长度就是段落的长度
totalLength += section.length
// 把这些段落放进文章中
article.push(section)
}
return article
}
生成文本index.js
回到index.js中
- 引入打造的方法
- 打造函数读取绝对路径下的语料库
- 选取文章标题
- 组合文章
- 将文本写入文件夹中
js
import fs from 'fs'
import { fileURLToPath } from 'url'
import { dirname, resolve } from 'path'
// 引入随机生成的文章
import { generate } from './lib/generator.js'
// 引入从语料库中随机选择的句子
import { createRandomPicker } from './lib/random.js'
// 读取当前脚本的绝对路径,将数据以对象形式返回
function loadCorpus(src) {
const url = import.meta.url
const path = resolve(dirname(fileURLToPath(url)), src)
const data = fs.readFileSync(path, { encoding: 'utf8' })
return JSON.parse(data)
}
const corpus = loadCorpus('curpus/data.json')
// 选取文章标题
const pickTitle = createRandomPicker(corpus.title)
const title = pickTitle()
// 生成文章
const article = generate(title, { corpus })
// 写入文章
fs.writeFileSync(`./output/${title}.txt`, article.join(''));
最后,文本生成,打开我们事先准备好的output文件夹,发现其中多出几个文件
恭喜!文章生成成功!