// Ultimate Poem Forge — 五言绝句终极穷举器(C + UTF‑8)
// By Ziyang-Bai Copyright 2025 Ziyang-Bai allrights reserved!
// ---------------------------------------------------------------------------
// 说明
// 本程序按“Unicode 数值顺序”的汉字表进行枚举,生成所有可能的五言绝句
//(4 句 × 每句 5 字,共 20 字)。
//
// 警告:
// 默认仅使用 CJK Unified Ideographs(U+4E00–U+9FFF)与扩展 A
// (U+3400–U+4DBF)。两个区合计 27,584 个汉字。全部 20 字位
// 的组合数约为 27,584^20,天文数量,任何机器都无法在有限时间内跑完。
// 程序提供“起始索引/计数/续跑/输出文件”等参数用于分片与实验。
//
// 你也可以通过“范围文件”添加更多扩展区(B、C、D、E、F、G、H…)。
// 范围文件格式:每行一个区间,十六进制起止码位,形如:
// 3400 4DBF
// 4E00 9FFF
// 行内用空白分隔,起止都包含(inclusive)。
//
// 枚举顺序:将所有可用码位按数值升序拼成一张“字表”,对 20 个字位做
// 基数为 |表| 的里程表递增(最右位先变)。输出 UTF‑8 文本:
// 每行 5 字,共 4 行;两首之间空一行。
//
// 构建:
// gcc -O3 -std=c11 -Wall -Wextra -o poem_forge poem_forge.c
// 运行示例(仅试跑 3 首,起始索引 0,输出到文件):
// ./poem_forge --count 3 --start 0 --out poems.txt
// 指定自定义范围文件(覆盖默认两个区):
// ./poem_forge --ranges ranges.txt --count 2
// 追加输出:
// ./poem_forge --start 1000000 --count 1000 --out part2.txt
//
// 参数:
// --start <uint64> 起始组合索引(默认 0)
// --count <uint64> 要生成的首数(默认无限,建议务必设置)
// --out <path> 输出文件(默认 stdout)
// --ranges <path> 使用范围文件(若未提供则用内置两个区)
// --checkpoint <n> 每生成 n 首在 stderr 报告一次进度(默认 0=不报)
//
// ---------------------------------------------------------------------------
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include <ctype.h>
#ifndef _WIN32
#include <unistd.h>
#endif
// 一首诗 4 句 × 每句 5 字
#define LINES 4
#define CHARS_PER_LINE 5
#define POEM_LEN (LINES * CHARS_PER_LINE)
typedef struct { uint32_t start, end; } Range; // inclusive
typedef struct {
uint32_t *table; // 升序的码位表
size_t size; // 表长
} CodeTable;
static int cmp_u32(const void *a, const void *b) {
uint32_t ua = *(const uint32_t*)a;
uint32_t ub = *(const uint32_t*)b;
if (ua < ub) return -1;
if (ua > ub) return 1;
return 0;
}
static int hex32(const char *s, uint32_t *out) {
// 解析十六进制(不需要 0x 前缀)
char *end = NULL;
unsigned long v = strtoul(s, &end, 16);
if (end == s || *end != '\0' || v > 0x10FFFFUL) return -1;
*out = (uint32_t)v;
return 0;
}
static int load_ranges_from_file(const char *path, Range **ranges_out, size_t *n_out) {
FILE *f = fopen(path, "r");
if (!f) return -1;
size_t cap = 16, n = 0;
Range *arr = (Range*)malloc(cap * sizeof(Range));
if (!arr) { fclose(f); return -1; }
char line[256];
while (fgets(line, sizeof(line), f)) {
// 跳过注释与空行
char *p = line; while (isspace((unsigned char)*p)) ++p;
if (*p == '\0' || *p == '#') continue;
char a[64], b[64];
if (sscanf(p, "%63s %63s", a, b) != 2) continue;
uint32_t s, e; if (hex32(a, &s) || hex32(b, &e)) continue;
if (s > e) { uint32_t t=s; s=e; e=t; }
if (n == cap) { cap *= 2; arr = (Range*)realloc(arr, cap*sizeof(Range)); if (!arr){ fclose(f); return -1; } }
arr[n++] = (Range){ s, e };
}
fclose(f);
*ranges_out = arr; *n_out = n; return 0;
}
static CodeTable build_table_from_ranges(const Range *ranges, size_t n) {
// 统计总数
uint64_t total = 0;
for (size_t i=0;i<n;i++) {
if (ranges[i].start > ranges[i].end) continue;
total += (uint64_t)ranges[i].end - (uint64_t)ranges[i].start + 1ULL;
}
if (total == 0) return (CodeTable){NULL,0};
if (total > SIZE_MAX/sizeof(uint32_t)) {
fprintf(stderr, "[fatal] 字表过大,无法分配内存\n");
return (CodeTable){NULL,0};
}
uint32_t *table = (uint32_t*)malloc((size_t)total * sizeof(uint32_t));
if (!table) return (CodeTable){NULL,0};
size_t k=0;
for (size_t i=0;i<n;i++) {
for (uint32_t cp = ranges[i].start; cp <= ranges[i].end; ++cp) {
table[k++] = cp;
if (cp == 0x10FFFF) break; // 防溢出保护
}
}
// 排序并去重(范围可能重叠)
qsort(table, k, sizeof(uint32_t), cmp_u32);
size_t u = 0;
for (size_t i=0;i<k;i++) {
if (i==0 || table[i]!=table[i-1]) table[u++] = table[i];
}
return (CodeTable){ table, u };
}
static CodeTable default_table(void) {
// 默认仅含:扩展 A(3400–4DBF)与基本区(4E00–9FFF)
Range r[] = {
{0x3400, 0x4DBF},
{0x4E00, 0x9FFF},
};
return build_table_from_ranges(r, sizeof(r)/sizeof(r[0]));
}
// 将 Unicode 码位编码为 UTF‑8,返回字节数(1–4),失败返回 0
static int utf8_encode(uint32_t cp, unsigned char out[4]) {
if (cp <= 0x7F) { out[0] = (unsigned char)cp; return 1; }
else if (cp <= 0x7FF) { out[0] = 0xC0 | (cp >> 6); out[1] = 0x80 | (cp & 0x3F); return 2; }
else if (cp <= 0xFFFF) {
// 排除 UTF‑16 代理项(不是有效字符)
if (cp >= 0xD800 && cp <= 0xDFFF) return 0;
out[0] = 0xE0 | (cp >> 12);
out[1] = 0x80 | ((cp >> 6) & 0x3F);
out[2] = 0x80 | (cp & 0x3F);
return 3;
} else if (cp <= 0x10FFFF) {
out[0] = 0xF0 | (cp >> 18);
out[1] = 0x80 | ((cp >> 12) & 0x3F);
out[2] = 0x80 | ((cp >> 6) & 0x3F);
out[3] = 0x80 | (cp & 0x3F);
return 4;
}
return 0;
}
// 写出一个码位到文件
static int write_cp_utf8(FILE *fp, uint32_t cp) {
unsigned char buf[4];
int n = utf8_encode(cp, buf);
if (n <= 0) return -1;
return (fwrite(buf, 1, (size_t)n, fp) == (size_t)n) ? 0 : -1;
}
// 迭代器:将“全局组合索引”映射到 20 位“数字”(基数为表长)
static void index_to_digits(uint64_t idx, size_t base, size_t digits[POEM_LEN]) {
for (int i = POEM_LEN - 1; i >= 0; --i) {
digits[i] = (size_t)(idx % base);
idx /= base;
}
}
static int increment_digits(size_t digits[POEM_LEN], size_t base) {
for (int i = POEM_LEN - 1; i >= 0; --i) {
if (++digits[i] < base) return 1; // 成功加一
digits[i] = 0;
}
return 0; // 溢出,已到尽头
}
static void usage(const char *argv0) {
fprintf(stderr,
"用法: %s [--start N] [--count M] [--out FILE] [--ranges FILE] [--checkpoint K]\n"
" --start N 起始组合索引(默认 0)\n"
" --count M 生成 M 首(默认直到穷尽;强烈建议设置)\n"
" --out FILE 输出文件(默认 stdout)\n"
" --ranges FILE 从文件加载码位区间(若不给则使用内置 A+基本区)\n"
" --checkpoint K 每生成 K 首在 stderr 报告一次进度\n",
argv0);
}
int main(int argc, char **argv) {
uint64_t start = 0;
uint64_t count = UINT64_MAX; // 不限
const char *outpath = NULL;
const char *ranges_path = NULL;
uint64_t checkpoint = 0;
for (int i=1;i<argc;i++) {
if (!strcmp(argv[i], "--start") && i+1<argc) { start = strtoull(argv[++i], NULL, 10); }
else if (!strcmp(argv[i], "--count") && i+1<argc) { count = strtoull(argv[++i], NULL, 10); }
else if (!strcmp(argv[i], "--out") && i+1<argc) { outpath = argv[++i]; }
else if (!strcmp(argv[i], "--ranges") && i+1<argc) { ranges_path = argv[++i]; }
else if (!strcmp(argv[i], "--checkpoint") && i+1<argc) { checkpoint = strtoull(argv[++i], NULL, 10); }
else { usage(argv[0]); return 1; }
}
Range *ranges = NULL; size_t n_ranges = 0; CodeTable tab = {0};
if (ranges_path) {
if (load_ranges_from_file(ranges_path, &ranges, &n_ranges) != 0 || n_ranges == 0) {
fprintf(stderr, "[error] 无法读取范围文件或为空:%s\n", ranges_path);
return 1;
}
tab = build_table_from_ranges(ranges, n_ranges);
} else {
tab = default_table();
}
if (!tab.table || tab.size == 0) {
fprintf(stderr, "[fatal] 字表为空\n");
return 1;
}
FILE *out = stdout;
if (outpath) {
out = fopen(outpath, "wb");
if (!out) {
fprintf(stderr, "[error] 打开输出文件失败:%s (%s)\n", outpath, strerror(errno));
free(tab.table); free(ranges); return 1;
}
}
// 提高 I/O 吞吐
setvbuf(out, NULL, _IOFBF, 1<<20); // 1 MiB 缓冲
// 初始化“里程表”
size_t base = tab.size;
size_t digits[POEM_LEN];
index_to_digits(start, base, digits);
uint64_t produced = 0;
uint64_t idx = start;
while (produced < count) {
// 输出一首:4 行 × 每行 5 字
for (int line = 0; line < LINES; ++line) {
for (int c = 0; c < CHARS_PER_LINE; ++c) {
size_t pos = (size_t)line * CHARS_PER_LINE + (size_t)c;
uint32_t cp = tab.table[ digits[pos] ];
if (write_cp_utf8(out, cp) != 0) { fprintf(stderr, "[error] 写入失败\n"); goto done; }
}
fputc('\n', out);
}
fputc('\n', out);
produced++; idx++;
if (checkpoint && (produced % checkpoint == 0)) {
fprintf(stderr, "[progress] start=%" PRIu64 ", emitted=%" PRIu64 " (idx=%" PRIu64 ")\n", start, produced, idx);
}
if (!increment_digits(digits, base)) {
break; // 穷尽
}
}
done:
if (out && out != stdout) fclose(out);
free(tab.table);
free(ranges);
if (produced < count) {
fprintf(stderr, " 已到字表的末尾或提前终止。总输出 %" PRIu64 " 首。\n", produced);
} else {
fprintf(stderr, " 正常结束。总输出 %" PRIu64 " 首,最后索引 %" PRIu64 "。\n", produced, idx-1);
}
return 0;
}
文章评论