如何一天生成万首乃至亿首优雅的烂诗

Author Avatar
学神之女 2020 年 08 月 12 日

原文:如何看待 14 岁突然火起来的岑怡诺,其简历称「一天能写 2000 首诗」? - 乔加西的回答 - 知乎

在线预览:How a Computer Writes Elegant Poems Rapidly

好诗不好生成,烂诗还不好生成?

我来写一个烂诗生成器,只要在自己的电脑、手机上跑一会,就可以生成出数万、数十万首诗。

第一步:整理出一些“优美”的词汇

对原文的此部分进行简单处理,按字数对它们分类:

const 词汇表 = {
  2: [],
  3: [],
  4: []
};
for(let 词儿 of `垂杨、新柳、玉丝纶、金嫩、柔条、曲岸垂杨、柳丝、闲柳、千花万柳、花海、芳甸吐嫩、摇绿、绿卷、浓翠、横翠、金碧、舒卷、款款、片片、缕缕、落纷纷、新晴、笼晴、韶光、婉媚、温丽、芊绵、春华、空灵、春柔、清婉、馨烈、和婉、清蕊、清芬、素约、红素、旖旎、飞花、浮花、落英、花影、芳丛、浮香、锦花、绣草、烂漫、芳踪、镂玉、雕琼、繁枝、素景、密叶、稠花、点水、扑面、风轻、水胧、倚暖、胭脂色、水溶溶、不留痕、几点春、邀春驻、花映柳、花深浅、树高低、翻微风、香接天、浓露飘香、软软东风、水逝云卷、繁花盈枝、百卉团团、和风轻暖、春光满树、秀色灼灼、红尘倦客、柳带摇风、香坠庭户、水阔花飞、瘦叶和风、叶叶心心、眼迷魂牵、浅粉深红`.split('、'))
  词汇表[词儿.length].push(词儿);

当然也可以用reduce方法来整理,效果是一样的:

const 词汇表 = `垂杨、新柳、玉丝纶、金嫩、柔条、曲岸垂杨、柳丝、闲柳、千花万柳、花海、芳甸吐嫩、摇绿、绿卷、浓翠、横翠、金碧、舒卷、款款、片片、缕缕、落纷纷、新晴、笼晴、韶光、婉媚、温丽、芊绵、春华、空灵、春柔、清婉、馨烈、和婉、清蕊、清芬、素约、红素、旖旎、飞花、浮花、落英、花影、芳丛、浮香、锦花、绣草、烂漫、芳踪、镂玉、雕琼、繁枝、素景、密叶、稠花、点水、扑面、风轻、水胧、倚暖、胭脂色、水溶溶、不留痕、几点春、邀春驻、花映柳、花深浅、树高低、翻微风、香接天、浓露飘香、软软东风、水逝云卷、繁花盈枝、百卉团团、和风轻暖、春光满树、秀色灼灼、红尘倦客、柳带摇风、香坠庭户、水阔花飞、瘦叶和风、叶叶心心、眼迷魂牵、浅粉深红`.split('、').reduce(
  (词汇表, 词儿) => {
    词汇表[词儿.length].push(词儿);
    return 词汇表;
  },
  {
    2: [],
    3: [],
    4: []
  }
);

在 Deno 或 Node.js 的交互式解释器里粘贴并执行上述代码,再执行词汇表,可以得到整理好的词汇:

{
  2: [
    "垂杨", "新柳", "金嫩", "柔条", "柳丝", "闲柳", "花海",
    "摇绿", "绿卷", "浓翠", "横翠", "金碧", "舒卷", "款款",
    "片片", "缕缕", "新晴", "笼晴", "韶光", "婉媚", "温丽",
    "芊绵", "春华", "空灵", "春柔", "清婉", "馨烈", "和婉",
    "清蕊", "清芬", "素约", "红素", "旖旎", "飞花", "浮花",
    "落英", "花影", "芳丛", "浮香", "锦花", "绣草", "烂漫",
    "芳踪", "镂玉", "雕琼", "繁枝", "素景", "密叶", "稠花",
    "点水", "扑面", "风轻", "水胧", "倚暖"
  ],
  3: [
    "玉丝纶", "落纷纷", "胭脂色",
    "水溶溶", "不留痕", "几点春",
    "邀春驻", "花映柳", "花深浅",
    "树高低", "翻微风", "香接天"
  ],
  4: [
    "曲岸垂杨", "千花万柳", "芳甸吐嫩",
    "浓露飘香", "软软东风", "水逝云卷",
    "繁花盈枝", "百卉团团", "和风轻暖",
    "春光满树", "秀色灼灼", "红尘倦客",
    "柳带摇风", "香坠庭户", "水阔花飞",
    "瘦叶和风", "叶叶心心", "眼迷魂牵",
    "浅粉深红"
  ]
}

如果不满足于这些词汇,可以去一些词典站爬一些,加载到数据库里。

第二步:以这些“优美”的词为主要词汇,排列组合,拼凑出一首烂诗,可酌情加词

我们先提供一些模板:

const 模板 = [
  //四言
  "4", "22", "3兮",
  //五言
  "23", "32", "4兮", "22兮", "是4", "是22",
  //六言
  "33", "42", "24", "222",
  //这里可以再兮一把,不过我懒得兮
  //七言
  "223", "43", "322", "34", "33兮", "42兮", "24兮", "222兮"
];

然后随机选定一个模板:

const 随机抽取 = 数组 => 数组[
  Math.round(
    Math.random() * (数组.length - 1)
  )
];
const 既定模板 = 随机抽取(模板);

套用模板偶数次,输出:

const 套模板 = 模板 => 模板.replace(
  /\d/g,
  长度 => 随机抽取(词汇表[长度])
);
console.log(
  Array((Math.random() * 2 | 0) * 2 + 4)
  .fill(既定模板) // 可以直接写成 .fill(随机抽取(模板))
  .map(套模板)
  .join('\n')
);

就是成诗。提一些例子:

  • 玉丝纶树高低兮
    不留痕树高低兮

树高低邀春驻兮
不留痕水溶溶兮

  • 是芳甸吐嫩
    是曲岸垂杨

是和风轻暖
是繁花盈枝
是香坠庭户
是秀色灼灼

  • 水溶溶软软东风
    玉丝纶秀色灼灼

邀春驻繁花盈枝
邀春驻香坠庭户
几点春香坠庭户
树高低繁花盈枝

如果我们希望诗句是错落的而不是等长的,先对模板和词汇表进行扩充:

模板.push(
  //一言
  "啊",
  //二言
  "2", "1啊", "1呢", "是1",
  //三言
  "3", "2呢", "2啊", "是2", "不是1"
  //四言、五言、六言的句子不适合带单字词
);
词汇表[1] = [
  "人", "你", "我", "她", "他",
  "它", "赤", "橙", "黄", "绿",
  "蓝", "靛", "紫", "春", "夏",
  "秋", "冬"
];

然后套用若干个模板,输出:

let 长度 = Math.random() * 10; // 不用取整
for(let i = 0; i < 长度; ++ i)
  console.log(套模板(随机抽取(模板)));
  • 是绿
    花深浅邀春驻

千花万柳兮
新柳绿卷浓翠兮

是秀色灼灼
花映柳浮香素约
我啊
片片旖旎
扑面落英兮

  • 几点春芳丛花影
    几点春秀色灼灼

翻微风稠花
是软软东风
稠花旖旎馨烈兮
树高低水阔花飞
几点春兮
绿卷香接天
百卉团团繁枝兮

这看着不押韵,心里难受是不是?如果要押韵的话,可以按照词汇的最后一个字的韵母,细致地整理词汇表。我不难受,所以这里我不弄。悄悄告诉你,博主有严重懒癌。

代码汇总

从上边零散的片段复制粘贴看着有些麻烦,所以这里我汇总成一段完整的代码。为让代码能与其它代码能够和谐相处,这里我使用英文变量名。

const vocabulary = {
  1: [
    "人", "你", "我", "她", "他",
    "它", "赤", "橙", "黄", "绿",
    "蓝", "靛", "紫", "春", "夏",
    "秋", "冬"
  ],
  2: [
    "垂杨", "新柳", "金嫩", "柔条", "柳丝", "闲柳", "花海",
    "摇绿", "绿卷", "浓翠", "横翠", "金碧", "舒卷", "款款",
    "片片", "缕缕", "新晴", "笼晴", "韶光", "婉媚", "温丽",
    "芊绵", "春华", "空灵", "春柔", "清婉", "馨烈", "和婉",
    "清蕊", "清芬", "素约", "红素", "旖旎", "飞花", "浮花",
    "落英", "花影", "芳丛", "浮香", "锦花", "绣草", "烂漫",
    "芳踪", "镂玉", "雕琼", "繁枝", "素景", "密叶", "稠花",
    "点水", "扑面", "风轻", "水胧", "倚暖"
  ],
  3: [
    "玉丝纶", "落纷纷", "胭脂色",
    "水溶溶", "不留痕", "几点春",
    "邀春驻", "花映柳", "花深浅",
    "树高低", "翻微风", "香接天"
  ],
  4: [
    "曲岸垂杨", "千花万柳", "芳甸吐嫩",
    "浓露飘香", "软软东风", "水逝云卷",
    "繁花盈枝", "百卉团团", "和风轻暖",
    "春光满树", "秀色灼灼", "红尘倦客",
    "柳带摇风", "香坠庭户", "水阔花飞",
    "瘦叶和风", "叶叶心心", "眼迷魂牵",
    "浅粉深红"
  ]
};
const templates = [ //For monospace poems.
  //4 characters
  "4", "22", "3兮",
  //5 characters
  "23", "32", "4兮", "22兮", "是4", "是22",
  //6 characters
  "33", "42", "24", "222",
  //Here I can add more templates with '兮' but I'm lazy to do it.
  //7 characters
  "223", "43", "322", "34", "33兮", "42兮", "24兮", "222兮"
];
const templatesForNonMonospacePoems = [
  ...templates,
  //1 character
  "啊",
  //2 characters
  "2", "1啊", "1呢", "是1",
  //3 characters
  "3", "2呢", "2啊", "是2", "不是1"
  //Single-character words are suitable for sentences with up to 3 characters.
];
const pickRandomly = from => from[
  Math.round(
    Math.random() * (from.length - 1)
  )
];
const applyTemplate = template => template.replace(
  /\d/g,
  length => randomlyPick(vocabulary[length])
);
//If you want it to be vivid, replace "generate" with "compose".
export function generateMonospacePoem() { //Returns an array of sentences of a poem.
  return Array((Math.random() * 2 | 0) * 2 + 4)
    .fill(randomlyPick(templates))
    .map(applyTemplate);
}
export function generateNonMonospacePoem() {
  const sentences = [];
  const length = Math.random() * 10;
  for(let i = 0; i < length; ++i)
    sentences.push(applyTemplate(randomlyPick(templatesForNonMonospacePoems)));
  return sentences;
}
export function generatePoem(){
  return Math.random() > .5 ? generateMonospacePoem() : generateNonMonospacePoem();
}

保存成poem-generator.js,就可以这么用它:

import {generatePoem} from './poem-generator.js';
console.time('作诗10000首');
for(let i = 0; i < 10000; ++i) generatePoem();
console.timeEnd('作诗10000首');

确保以上代码在 Deno 或有type="module"<script>标签中运行,否则可能报错。

作诗 10000 首(像上边那样空转不输出),只要 100~200ms。

如果手机上装 Deno 或 Node.js 有点麻烦的话,可以进入文章开头的 在线预览 。(为展示效果,生成速度被限制,每秒只能生成 1 首诗)这样我们就都可以高产烂诗!

又水出一篇文章Σ(っ °Д °;)っ想发评论表达,发现评论发不出来……