hanzi-chai 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/affine.d.ts +8 -0
- package/dist/affine.js +153 -0
- package/dist/assembly.d.ts +63 -0
- package/dist/assembly.js +286 -0
- package/dist/bezier.d.ts +74 -0
- package/dist/bezier.js +140 -0
- package/dist/classifier.d.ts +109 -0
- package/dist/classifier.js +108 -0
- package/dist/component.d.ts +115 -0
- package/dist/component.js +278 -0
- package/dist/compound.d.ts +64 -0
- package/dist/compound.js +652 -0
- package/dist/config.d.ts +236 -0
- package/dist/config.js +48 -0
- package/dist/data/cjk.txt +20902 -0
- package/dist/data/data/cjk.txt +20902 -0
- package/dist/data/data/dictionary.txt +69429 -0
- package/dist/data/data/frequency.txt +263137 -0
- package/dist/data/data/key_distribution.txt +46 -0
- package/dist/data/data/pair_equivalence.txt +2116 -0
- package/dist/data/data/repertoire.json.deflate +0 -0
- package/dist/data/data/tygf.txt +8105 -0
- package/dist/data/dictionary.txt +69429 -0
- package/dist/data/frequency.txt +263137 -0
- package/dist/data/key_distribution.txt +46 -0
- package/dist/data/pair_equivalence.txt +2116 -0
- package/dist/data/repertoire.json.deflate +0 -0
- package/dist/data/tygf.txt +8105 -0
- package/dist/data.d.ts +195 -0
- package/dist/data.js +18 -0
- package/dist/degenerator.d.ts +21 -0
- package/dist/degenerator.js +134 -0
- package/dist/element.d.ts +56 -0
- package/dist/element.js +225 -0
- package/dist/equivalence.d.ts +21 -0
- package/dist/equivalence.js +74 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +126 -0
- package/dist/index.mjs +3553 -0
- package/dist/mathjs.d.ts +10 -0
- package/dist/mathjs.js +9 -0
- package/dist/repertoire.d.ts +75 -0
- package/dist/repertoire.js +406 -0
- package/dist/selector.d.ts +110 -0
- package/dist/selector.js +310 -0
- package/dist/topology.d.ts +41 -0
- package/dist/topology.js +209 -0
- package/dist/transformer.d.ts +18 -0
- package/dist/transformer.js +111 -0
- package/dist/utils.d.ts +107 -0
- package/dist/utils.js +546 -0
- package/package.json +34 -0
package/dist/mathjs.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 为了更好的性能,替代mathjs的部分函数 */
|
|
2
|
+
import type { N2 } from "./data.js";
|
|
3
|
+
export declare const add: (a: N2, b: N2) => N2;
|
|
4
|
+
export declare const subtract: (a: N2, b: N2) => N2;
|
|
5
|
+
export declare const multiply: (a: number, b: N2) => N2;
|
|
6
|
+
export declare const divide: (a: N2, b: number) => N2;
|
|
7
|
+
export declare const dot: (a: N2, b: N2) => number;
|
|
8
|
+
export declare const cross: (a: N2, b: N2) => number;
|
|
9
|
+
export declare const mean: (a: N2) => number;
|
|
10
|
+
export declare const distance: (a: N2, b: N2) => number;
|
package/dist/mathjs.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** 为了更好的性能,替代mathjs的部分函数 */
|
|
2
|
+
export const add = (a, b) => [a[0] + b[0], a[1] + b[1]];
|
|
3
|
+
export const subtract = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
4
|
+
export const multiply = (a, b) => [a * b[0], a * b[1]];
|
|
5
|
+
export const divide = (a, b) => [a[0] / b, a[1] / b];
|
|
6
|
+
export const dot = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
7
|
+
export const cross = (a, b) => a[0] * b[1] - a[1] * b[0];
|
|
8
|
+
export const mean = (a) => (a[0] + a[1]) / 2;
|
|
9
|
+
export const distance = (a, b) => Math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ComponentResults } from "./component.js";
|
|
2
|
+
import type { CompoundResults } from "./compound.js";
|
|
3
|
+
import type { Algebra, Analysis, CustomGlyph, CustomReadings, Element, Value } from "./config.js";
|
|
4
|
+
import type { Repertoire, PrimitiveRepertoire, Glyph } from "./data.js";
|
|
5
|
+
import { type Dictionary } from "./utils.js";
|
|
6
|
+
export declare const findGlyphIndex: (glyphs: Glyph[], tags: string[]) => number;
|
|
7
|
+
/**
|
|
8
|
+
* 将原始字符集转换为字符集
|
|
9
|
+
* 主要的工作是对每个字符,在数据库中的多个字形中选取一个
|
|
10
|
+
*
|
|
11
|
+
* @param repertoire - 原始字符集
|
|
12
|
+
* @param customGlyph - 自定义字形
|
|
13
|
+
* @param customReadings - 自定义读音
|
|
14
|
+
* @param tags - 用户选择的标签
|
|
15
|
+
*
|
|
16
|
+
* 基本逻辑为,对于每个字符,
|
|
17
|
+
* - 如果用户指定了字形,则使用用户指定的字形
|
|
18
|
+
* - 如果用户指定的某个标签匹配上了这个字符的某个字形,则使用这个字形
|
|
19
|
+
* - 如果都没有,就使用默认字形
|
|
20
|
+
*/
|
|
21
|
+
export declare const determine: (repertoire: PrimitiveRepertoire, customGlyph?: CustomGlyph, customReadings?: CustomReadings, tags?: string[]) => Repertoire;
|
|
22
|
+
export interface AnalysisResult {
|
|
23
|
+
componentResults: ComponentResults;
|
|
24
|
+
componentError: string[];
|
|
25
|
+
customizations: ComponentResults;
|
|
26
|
+
customized: ComponentResults;
|
|
27
|
+
compoundResults: CompoundResults;
|
|
28
|
+
compoundError: string[];
|
|
29
|
+
rootSequence: Map<string, number[]>;
|
|
30
|
+
}
|
|
31
|
+
export interface AnalysisConfig {
|
|
32
|
+
analysis: Analysis;
|
|
33
|
+
roots: Map<Element, Value>;
|
|
34
|
+
optionalRoots: Set<Element>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 确定需要分析的字符
|
|
38
|
+
*/
|
|
39
|
+
export declare const getRequiredTargets: (repertoire: Repertoire, config: AnalysisConfig, characters: string[]) => {
|
|
40
|
+
components: Set<string>;
|
|
41
|
+
compounds: Set<string>;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* 对整个字符集中的字符进行拆分
|
|
45
|
+
*
|
|
46
|
+
* @param repertoire - 字符集
|
|
47
|
+
* @param config - 配置
|
|
48
|
+
*/
|
|
49
|
+
export declare const analysis: (repertoire: Repertoire, config: AnalysisConfig, characters: string[]) => AnalysisResult;
|
|
50
|
+
interface 动态拆分 {
|
|
51
|
+
汉字信息: {
|
|
52
|
+
汉字: string;
|
|
53
|
+
通规: 0 | 1 | 2 | 3;
|
|
54
|
+
gb2312: 0 | 1 | 2;
|
|
55
|
+
频率: number;
|
|
56
|
+
读音: {
|
|
57
|
+
拼音: string;
|
|
58
|
+
频率: number;
|
|
59
|
+
[key: string]: any;
|
|
60
|
+
}[];
|
|
61
|
+
字块: string[];
|
|
62
|
+
}[];
|
|
63
|
+
多字词信息: {
|
|
64
|
+
词: string;
|
|
65
|
+
频率: number;
|
|
66
|
+
读音: {
|
|
67
|
+
拼音: string;
|
|
68
|
+
[key: string]: any;
|
|
69
|
+
}[];
|
|
70
|
+
}[];
|
|
71
|
+
动态拆分: Record<string, string[][]>;
|
|
72
|
+
字根笔画: Record<string, number[]>;
|
|
73
|
+
}
|
|
74
|
+
export declare const dynamicAnalysis: (repertoire: Repertoire, config: AnalysisConfig, characters: string[], dictionary: Dictionary, adaptedFrequency: Map<string, number>, algebra: Algebra) => 动态拆分;
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { isEqual, sortBy } from "lodash-es";
|
|
2
|
+
import { mergeClassifier } from "./classifier.js";
|
|
3
|
+
import { computeComponent, disassembleComponents, getComponentScheme, recursiveRenderComponent, renderRootList, } from "./component.js";
|
|
4
|
+
import { disassembleCompounds, recursiveRenderCompound, topologicalSort, } from "./compound.js";
|
|
5
|
+
import { range } from "d3-array";
|
|
6
|
+
import { applyRules } from "./element.js";
|
|
7
|
+
import { getDummyBasicComponent, isPUA } from "./utils.js";
|
|
8
|
+
export const findGlyphIndex = (glyphs, tags) => {
|
|
9
|
+
for (const tag of tags) {
|
|
10
|
+
const withTag = glyphs.findIndex((x) => (x.tags ?? []).includes(tag));
|
|
11
|
+
if (withTag !== -1)
|
|
12
|
+
return withTag;
|
|
13
|
+
}
|
|
14
|
+
return 0;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 将原始字符集转换为字符集
|
|
18
|
+
* 主要的工作是对每个字符,在数据库中的多个字形中选取一个
|
|
19
|
+
*
|
|
20
|
+
* @param repertoire - 原始字符集
|
|
21
|
+
* @param customGlyph - 自定义字形
|
|
22
|
+
* @param customReadings - 自定义读音
|
|
23
|
+
* @param tags - 用户选择的标签
|
|
24
|
+
*
|
|
25
|
+
* 基本逻辑为,对于每个字符,
|
|
26
|
+
* - 如果用户指定了字形,则使用用户指定的字形
|
|
27
|
+
* - 如果用户指定的某个标签匹配上了这个字符的某个字形,则使用这个字形
|
|
28
|
+
* - 如果都没有,就使用默认字形
|
|
29
|
+
*/
|
|
30
|
+
export const determine = (repertoire, customGlyph = {}, customReadings = {}, tags = []) => {
|
|
31
|
+
const determined = {};
|
|
32
|
+
const glyphCache = new Map();
|
|
33
|
+
for (const [name, character] of Object.entries(repertoire)) {
|
|
34
|
+
const { ambiguous: _, glyphs, readings, ...rest } = character;
|
|
35
|
+
const selectedIndex = findGlyphIndex(glyphs, tags);
|
|
36
|
+
const rawglyph = customGlyph[name] ?? glyphs[selectedIndex] ?? getDummyBasicComponent();
|
|
37
|
+
const glyph = recursiveHandleRawGlyph(rawglyph, glyphCache, repertoire);
|
|
38
|
+
const finalReadings = customReadings[name] ?? readings;
|
|
39
|
+
const determined_character = {
|
|
40
|
+
...rest,
|
|
41
|
+
glyph,
|
|
42
|
+
readings: finalReadings,
|
|
43
|
+
};
|
|
44
|
+
determined[name] = determined_character;
|
|
45
|
+
}
|
|
46
|
+
return determined;
|
|
47
|
+
};
|
|
48
|
+
function recursiveHandleRawGlyph(glyph, glyphCache, repertoire) {
|
|
49
|
+
if (glyph.type === "derived_component" ||
|
|
50
|
+
glyph.type === "spliced_component") {
|
|
51
|
+
const svgglyph = recursiveRenderComponent(glyph, repertoire, glyphCache);
|
|
52
|
+
return {
|
|
53
|
+
type: "basic_component",
|
|
54
|
+
tags: glyph.tags,
|
|
55
|
+
strokes: svgglyph instanceof Error ? [] : svgglyph,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
else if (glyph.type === "identity") {
|
|
59
|
+
return recursiveHandleRawGlyph(repertoire[glyph.source].glyphs[0], glyphCache, repertoire);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
return glyph;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const getRootSequence = (repertoire, config) => {
|
|
66
|
+
const classifier = mergeClassifier(config.analysis?.classifier);
|
|
67
|
+
const findSequence = (x) => {
|
|
68
|
+
if (x.match(/[0-9]+/)) {
|
|
69
|
+
return [...x].map(Number);
|
|
70
|
+
}
|
|
71
|
+
const glyph = repertoire[x]?.glyph;
|
|
72
|
+
if (glyph === undefined) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
if (glyph.type === "basic_component") {
|
|
76
|
+
return glyph.strokes.map((s) => classifier[s.feature]);
|
|
77
|
+
}
|
|
78
|
+
const sequence = recursiveRenderCompound(glyph, repertoire);
|
|
79
|
+
if (sequence instanceof Error)
|
|
80
|
+
return [];
|
|
81
|
+
return sequence.strokes.map((s) => classifier[s.feature]);
|
|
82
|
+
};
|
|
83
|
+
const rootSequence = new Map();
|
|
84
|
+
const roots = new Set([
|
|
85
|
+
...config.roots.keys(),
|
|
86
|
+
...config.optionalRoots.keys(),
|
|
87
|
+
]);
|
|
88
|
+
for (const root of roots) {
|
|
89
|
+
rootSequence.set(root, findSequence(root));
|
|
90
|
+
}
|
|
91
|
+
for (const num of Object.values(classifier)) {
|
|
92
|
+
rootSequence.set(num.toString(), [num]);
|
|
93
|
+
}
|
|
94
|
+
return rootSequence;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* 确定需要分析的字符
|
|
98
|
+
*/
|
|
99
|
+
export const getRequiredTargets = (repertoire, config, characters) => {
|
|
100
|
+
const queue = [...characters];
|
|
101
|
+
const components = new Set();
|
|
102
|
+
const compounds = new Set();
|
|
103
|
+
const knownSet = new Set(characters);
|
|
104
|
+
while (queue.length) {
|
|
105
|
+
const char = queue.shift();
|
|
106
|
+
const glyph = repertoire[char].glyph;
|
|
107
|
+
if (glyph.type === "compound") {
|
|
108
|
+
compounds.add(char);
|
|
109
|
+
const isCurrentRoot = config.roots.has(char);
|
|
110
|
+
const isOptionalRoot = config.optionalRoots.has(char);
|
|
111
|
+
if (isCurrentRoot && !isOptionalRoot)
|
|
112
|
+
continue;
|
|
113
|
+
glyph.operandList.forEach((x) => {
|
|
114
|
+
if (!knownSet.has(x)) {
|
|
115
|
+
knownSet.add(x);
|
|
116
|
+
queue.push(x);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
components.add(char);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { components, compounds };
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* 对整个字符集中的字符进行拆分
|
|
128
|
+
*
|
|
129
|
+
* @param repertoire - 字符集
|
|
130
|
+
* @param config - 配置
|
|
131
|
+
*/
|
|
132
|
+
export const analysis = (repertoire, config, characters) => {
|
|
133
|
+
const { components, compounds } = getRequiredTargets(repertoire, config, characters);
|
|
134
|
+
const [componentResults, componentError] = disassembleComponents(repertoire, config, characters, components);
|
|
135
|
+
const customizations = new Map();
|
|
136
|
+
const customConfig = config.analysis?.customize ?? {};
|
|
137
|
+
for (const [component, sequence] of Object.entries(customConfig)) {
|
|
138
|
+
const previousResult = componentResults.get(component);
|
|
139
|
+
if (previousResult === undefined)
|
|
140
|
+
continue;
|
|
141
|
+
if (previousResult !== undefined && "best" in previousResult) {
|
|
142
|
+
const maybeBetter = previousResult.schemes.find((x) => {
|
|
143
|
+
const list = x.scheme.map((y) => previousResult.map.get(y));
|
|
144
|
+
return isEqual(list, sequence);
|
|
145
|
+
});
|
|
146
|
+
if (maybeBetter !== undefined) {
|
|
147
|
+
const genuineResult = {
|
|
148
|
+
...previousResult,
|
|
149
|
+
best: maybeBetter,
|
|
150
|
+
sequence: sequence,
|
|
151
|
+
full: sequence,
|
|
152
|
+
};
|
|
153
|
+
customizations.set(component, genuineResult);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const pseudoResult = {
|
|
158
|
+
strokes: 0,
|
|
159
|
+
sequence: sequence,
|
|
160
|
+
full: sequence,
|
|
161
|
+
operator: undefined,
|
|
162
|
+
};
|
|
163
|
+
customizations.set(component, pseudoResult);
|
|
164
|
+
}
|
|
165
|
+
const all = new Set([...config.roots.keys()]);
|
|
166
|
+
for (const [component, sequenceList] of Object.entries(config.analysis?.dynamic_customize ?? {})) {
|
|
167
|
+
const previousResult = componentResults.get(component);
|
|
168
|
+
for (const sequence of sequenceList) {
|
|
169
|
+
if (!sequence.every((x) => /\d/.test(x) || all.has(x)))
|
|
170
|
+
continue;
|
|
171
|
+
const pseudoResult = {
|
|
172
|
+
...previousResult,
|
|
173
|
+
sequence: sequence,
|
|
174
|
+
full: sequence,
|
|
175
|
+
};
|
|
176
|
+
customizations.set(component, pseudoResult);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const customized = new Map([...componentResults, ...customizations]);
|
|
181
|
+
const [compoundResults, compoundError] = disassembleCompounds(repertoire, config, customized, characters, compounds);
|
|
182
|
+
const rootSequence = getRootSequence(repertoire, config);
|
|
183
|
+
return {
|
|
184
|
+
componentResults,
|
|
185
|
+
componentError,
|
|
186
|
+
customizations,
|
|
187
|
+
customized,
|
|
188
|
+
compoundResults,
|
|
189
|
+
compoundError,
|
|
190
|
+
rootSequence,
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
export const dynamicAnalysis = (repertoire, config, characters, dictionary, adaptedFrequency, algebra) => {
|
|
194
|
+
const { components, compounds } = getRequiredTargets(repertoire, config, characters);
|
|
195
|
+
const [componentResults, componentError] = disassembleComponents(repertoire, config, characters, components);
|
|
196
|
+
// 字块映射:把一个字表示为若干个字块
|
|
197
|
+
const segmentMap = new Map();
|
|
198
|
+
// 动态拆分:一个字块有若干种拆分方式
|
|
199
|
+
const segmentDynamicAnalysis = new Map();
|
|
200
|
+
// 部首映射:只用于飞花
|
|
201
|
+
const bushouMap = new Map();
|
|
202
|
+
// 处理部件作为字块的动态拆分,而字块映射是它自己
|
|
203
|
+
for (const [name, analysis] of componentResults) {
|
|
204
|
+
const maybeCustomDynamicAnalysis = config.analysis.dynamic_customize?.[name];
|
|
205
|
+
const dynamicAnalysis = "schemes" in analysis
|
|
206
|
+
? analysis.schemes.filter((x) => x.optional).map((x) => x.roots)
|
|
207
|
+
: [analysis.sequence];
|
|
208
|
+
segmentDynamicAnalysis.set(name, maybeCustomDynamicAnalysis ?? dynamicAnalysis);
|
|
209
|
+
segmentMap.set(name, [name]);
|
|
210
|
+
}
|
|
211
|
+
const sortedCompounds = topologicalSort(repertoire, compounds, config);
|
|
212
|
+
const rootMap = renderRootList(repertoire, [
|
|
213
|
+
...config.roots.keys(),
|
|
214
|
+
...config.optionalRoots.keys(),
|
|
215
|
+
]);
|
|
216
|
+
const classifier = mergeClassifier(config.analysis?.classifier);
|
|
217
|
+
const rootList = [...rootMap.values()];
|
|
218
|
+
const cachedGetSegment = (part, start, end) => {
|
|
219
|
+
const character = repertoire[part];
|
|
220
|
+
const name = `${isPUA(part) ? character.name : part}.${start}-${end ?? 0}`;
|
|
221
|
+
if (segmentDynamicAnalysis.has(name)) {
|
|
222
|
+
return name;
|
|
223
|
+
}
|
|
224
|
+
if (character.glyph.type !== "basic_component") {
|
|
225
|
+
console.log(`Cannot segment non-basic component: ${part}`);
|
|
226
|
+
return name;
|
|
227
|
+
}
|
|
228
|
+
const glyph = character.glyph.strokes.slice(start, end);
|
|
229
|
+
const cache = computeComponent(name, glyph);
|
|
230
|
+
console.log(`Part: ${part} (${part.codePointAt(0)}) [${start}${end ? `-${end}` : ""}]`);
|
|
231
|
+
const analysis = getComponentScheme(cache, rootList, config, classifier);
|
|
232
|
+
segmentDynamicAnalysis.set(name, analysis.schemes.filter((x) => x.optional).map((x) => x.roots));
|
|
233
|
+
segmentMap.set(name, [name]);
|
|
234
|
+
return name;
|
|
235
|
+
};
|
|
236
|
+
// 只用于飞花
|
|
237
|
+
const fullSegmentMap = new Map(segmentMap.entries());
|
|
238
|
+
// 处理复合体的字块映射,如果它同时也作为字块,就处理它的动态拆分
|
|
239
|
+
for (const [name, compound] of sortedCompounds) {
|
|
240
|
+
const isCurrentRoots = config.roots.has(name);
|
|
241
|
+
const isOptionalRoots = config.optionalRoots.has(name);
|
|
242
|
+
// 如果复合体是必选字根,则直接视为一个字块
|
|
243
|
+
if (isCurrentRoots && !isOptionalRoots) {
|
|
244
|
+
segmentDynamicAnalysis.set(name, [[name]]);
|
|
245
|
+
segmentMap.set(name, [name]);
|
|
246
|
+
fullSegmentMap.set(name, [name]);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
// 找出复合体对应的字块
|
|
250
|
+
const glyph = compound.glyph;
|
|
251
|
+
const componentList = [];
|
|
252
|
+
const order = glyph.order ??
|
|
253
|
+
glyph.operandList.map((_, i) => ({ index: i, strokes: 0 }));
|
|
254
|
+
if (config.analysis.serializer === "qingyun") {
|
|
255
|
+
const seenIndex = new Map();
|
|
256
|
+
for (const { index, strokes } of order) {
|
|
257
|
+
const part = glyph.operandList[index];
|
|
258
|
+
if (strokes === 0) {
|
|
259
|
+
if (!seenIndex.has(index)) {
|
|
260
|
+
componentList.push(...segmentMap.get(part));
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
const start = seenIndex.get(index);
|
|
264
|
+
const name = cachedGetSegment(part, start);
|
|
265
|
+
componentList.push(name);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
const start = seenIndex.get(index) ?? 0;
|
|
270
|
+
const end = start + strokes;
|
|
271
|
+
const name = cachedGetSegment(part, start, end);
|
|
272
|
+
componentList.push(name);
|
|
273
|
+
seenIndex.set(index, end);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else if (config.analysis.serializer === "feihua") {
|
|
278
|
+
// 飞花需要处理部首
|
|
279
|
+
const sortedOperandList = sortBy(range(glyph.operandList.length), (i) => order.findIndex((b) => b.index === i)).map((i) => glyph.operandList[i]);
|
|
280
|
+
const fullComponentList = [];
|
|
281
|
+
for (const child of sortedOperandList) {
|
|
282
|
+
fullComponentList.push(...fullSegmentMap.get(child));
|
|
283
|
+
}
|
|
284
|
+
fullSegmentMap.set(name, fullComponentList);
|
|
285
|
+
// 判断部首:第一个或最后一个恰好为一字根
|
|
286
|
+
const first = sortedOperandList[0];
|
|
287
|
+
const last = sortedOperandList.at(-1);
|
|
288
|
+
const firstDynamic = segmentDynamicAnalysis.get(first);
|
|
289
|
+
const lastDynamic = segmentDynamicAnalysis.get(last);
|
|
290
|
+
const res = [];
|
|
291
|
+
if (/[⿴⿵⿶⿷⿸⿹⿺⿻]/.test(glyph.operator) &&
|
|
292
|
+
componentResults.has(glyph.operandList[0])) {
|
|
293
|
+
bushouMap.set(name, segmentDynamicAnalysis.get(glyph.operandList[0])[0][0]);
|
|
294
|
+
res.push(...glyph.operandList.slice(1));
|
|
295
|
+
}
|
|
296
|
+
else if (firstDynamic?.[0]?.length === 1 &&
|
|
297
|
+
lastDynamic?.[0]?.length === 1 &&
|
|
298
|
+
/[又]/.test(firstDynamic[0][0])) {
|
|
299
|
+
bushouMap.set(name, lastDynamic[0][0]);
|
|
300
|
+
res.push(...sortedOperandList.slice(0, -1));
|
|
301
|
+
}
|
|
302
|
+
else if (firstDynamic?.[0]?.length === 1) {
|
|
303
|
+
bushouMap.set(name, firstDynamic[0][0]);
|
|
304
|
+
res.push(...sortedOperandList.slice(1));
|
|
305
|
+
}
|
|
306
|
+
else if (lastDynamic?.[0]?.length === 1) {
|
|
307
|
+
bushouMap.set(name, lastDynamic[0][0]);
|
|
308
|
+
res.push(...sortedOperandList.slice(0, -1));
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
const firstSegments = fullSegmentMap.get(first);
|
|
312
|
+
const segment = firstSegments[0];
|
|
313
|
+
const bushou = segmentDynamicAnalysis.get(segment)[0][0];
|
|
314
|
+
bushouMap.set(name, bushou);
|
|
315
|
+
res.push(...sortedOperandList.slice(1));
|
|
316
|
+
}
|
|
317
|
+
if (res.length === 1) {
|
|
318
|
+
const only = res[0];
|
|
319
|
+
if (!sortedCompounds.has(only)) {
|
|
320
|
+
componentList.push(...fullSegmentMap.get(only).slice(0, 2));
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const subglyph = sortedCompounds.get(only).glyph;
|
|
324
|
+
const order = subglyph.order ??
|
|
325
|
+
subglyph.operandList.map((_, i) => ({ index: i, strokes: 0 }));
|
|
326
|
+
const sortedOperandList = sortBy(range(subglyph.operandList.length), (i) => order.findIndex((b) => b.index === i)).map((i) => subglyph.operandList[i]);
|
|
327
|
+
for (const part of sortedOperandList) {
|
|
328
|
+
componentList.push(...fullSegmentMap.get(part)[0]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
for (const child of res) {
|
|
334
|
+
componentList.push(fullSegmentMap.get(child)[0]);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
const sortedOperandList = sortBy(range(glyph.operandList.length), (i) => order.findIndex((b) => b.index === i)).map((i) => glyph.operandList[i]);
|
|
340
|
+
for (const child of sortedOperandList) {
|
|
341
|
+
componentList.push(...segmentMap.get(child));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (isOptionalRoots) {
|
|
345
|
+
let dynamic = [[]];
|
|
346
|
+
for (const operand of componentList) {
|
|
347
|
+
const dynamicAnalysis = segmentDynamicAnalysis.get(operand);
|
|
348
|
+
dynamic = dynamic
|
|
349
|
+
.map((x) => dynamicAnalysis.map((y) => x.concat(y)))
|
|
350
|
+
.flat();
|
|
351
|
+
}
|
|
352
|
+
segmentDynamicAnalysis.set(name, [[name]].concat(dynamic));
|
|
353
|
+
segmentMap.set(name, [name]);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
segmentMap.set(name, componentList);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const 汉字信息 = characters.map((x) => {
|
|
360
|
+
const readings = repertoire[x]?.readings ?? [];
|
|
361
|
+
const frequency = adaptedFrequency.get(x) ?? 0;
|
|
362
|
+
const frequencies = readings.map(({ pinyin, importance }) => {
|
|
363
|
+
const entry = {
|
|
364
|
+
拼音: pinyin,
|
|
365
|
+
频率: Math.round((frequency * importance) / 100),
|
|
366
|
+
};
|
|
367
|
+
for (const [name, rules] of Object.entries(algebra)) {
|
|
368
|
+
entry[name] = applyRules(name, rules, pinyin);
|
|
369
|
+
}
|
|
370
|
+
return entry;
|
|
371
|
+
});
|
|
372
|
+
return {
|
|
373
|
+
汉字: x,
|
|
374
|
+
通规: repertoire[x].tygf,
|
|
375
|
+
gb2312: repertoire[x].gb2312,
|
|
376
|
+
频率: frequency,
|
|
377
|
+
读音: frequencies,
|
|
378
|
+
字块: segmentMap.get(x) ?? [],
|
|
379
|
+
部首: bushouMap.get(x),
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
const 多字词信息 = dictionary.map(([word, syllables]) => {
|
|
383
|
+
const frequency = adaptedFrequency.get(word) ?? 0;
|
|
384
|
+
const readings = syllables.split(" ").map((pinyin) => {
|
|
385
|
+
const entry = {
|
|
386
|
+
拼音: pinyin,
|
|
387
|
+
};
|
|
388
|
+
for (const [name, rules] of Object.entries(algebra)) {
|
|
389
|
+
entry[name] = applyRules(name, rules, pinyin);
|
|
390
|
+
}
|
|
391
|
+
return entry;
|
|
392
|
+
});
|
|
393
|
+
return {
|
|
394
|
+
词: word,
|
|
395
|
+
频率: frequency,
|
|
396
|
+
读音: readings,
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
const result = {
|
|
400
|
+
汉字信息,
|
|
401
|
+
多字词信息,
|
|
402
|
+
动态拆分: Object.fromEntries(segmentDynamicAnalysis),
|
|
403
|
+
字根笔画: Object.fromEntries(getRootSequence(repertoire, config)),
|
|
404
|
+
};
|
|
405
|
+
return result;
|
|
406
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type ComputedComponent, NoSchemeError } from "./component.js";
|
|
2
|
+
import { type Analysis, type Element, type Value, type SieveName } from "./config.js";
|
|
3
|
+
import type { AnalysisConfig } from "./repertoire.js";
|
|
4
|
+
export declare const defaultSelector: SieveName[];
|
|
5
|
+
export type Scheme = number[];
|
|
6
|
+
type Comparable = number | number[];
|
|
7
|
+
export interface Environment {
|
|
8
|
+
component: ComputedComponent;
|
|
9
|
+
rootMap: Map<number, string>;
|
|
10
|
+
analysis: Analysis;
|
|
11
|
+
roots: Map<Element, Value>;
|
|
12
|
+
}
|
|
13
|
+
interface Sieve<T extends Comparable> {
|
|
14
|
+
title: SieveName;
|
|
15
|
+
key: (scheme: Scheme, environment: Environment) => T;
|
|
16
|
+
display?: (data: T) => string;
|
|
17
|
+
}
|
|
18
|
+
export declare function isLess<T extends Comparable>(a: T, b: T): boolean;
|
|
19
|
+
export declare const length: Sieve<number>;
|
|
20
|
+
/**
|
|
21
|
+
* 规则:取大优先
|
|
22
|
+
*
|
|
23
|
+
* 让顺序靠前的字根尽量取到更多的笔画
|
|
24
|
+
*/
|
|
25
|
+
export declare const bias: Sieve<number[]>;
|
|
26
|
+
/**
|
|
27
|
+
* 规则:取小优先
|
|
28
|
+
*
|
|
29
|
+
* 让顺序靠前的字根尽量取到更少的笔画
|
|
30
|
+
*/
|
|
31
|
+
export declare const unbias: Sieve<number[]>;
|
|
32
|
+
/**
|
|
33
|
+
* 规则:全符笔顺
|
|
34
|
+
*
|
|
35
|
+
* 各个字根中笔画的顺序连在一起是否与整个部件的笔顺相同,相同者优先
|
|
36
|
+
* 该规则采集自三码郑码的文档
|
|
37
|
+
*
|
|
38
|
+
* @see https://www.yuque.com/smzm/zhengma/otb32d
|
|
39
|
+
*/
|
|
40
|
+
export declare const order: Sieve<number>;
|
|
41
|
+
/**
|
|
42
|
+
* 规则:连续笔顺
|
|
43
|
+
*
|
|
44
|
+
* 字根是否由部件中连续的几个笔画构成,不符合此特点的字根数量少者优先。该规则采集自宇浩输入法的文档。
|
|
45
|
+
*
|
|
46
|
+
* @see https://zhuyuhao.com/yuhao/docs/learn.html#符合笔顺
|
|
47
|
+
*/
|
|
48
|
+
export declare const order2: Sieve<number>;
|
|
49
|
+
/**
|
|
50
|
+
* 规则:非形近根
|
|
51
|
+
*
|
|
52
|
+
* 尽量少使用被归并到其他字根的字根。该规则采集自三码郑码的文档
|
|
53
|
+
*
|
|
54
|
+
* @see https://www.yuque.com/smzm/zhengma/otb32d
|
|
55
|
+
*/
|
|
56
|
+
export declare const similar: Sieve<number>;
|
|
57
|
+
/**
|
|
58
|
+
* 规则:强字根
|
|
59
|
+
*
|
|
60
|
+
* 尽量多使用特定的一些字根。该规则采集自郑码的文档
|
|
61
|
+
*/
|
|
62
|
+
export declare const strong: Sieve<number>;
|
|
63
|
+
/**
|
|
64
|
+
* 规则:弱字根
|
|
65
|
+
*
|
|
66
|
+
* 尽量避免使用特定的一些字根
|
|
67
|
+
*/
|
|
68
|
+
export declare const weak: Sieve<number>;
|
|
69
|
+
/**
|
|
70
|
+
* 规则:能连不交
|
|
71
|
+
*
|
|
72
|
+
* 尽量让字根不交叉。该规则采集自五笔
|
|
73
|
+
*/
|
|
74
|
+
export declare const crossing: Sieve<number>;
|
|
75
|
+
/**
|
|
76
|
+
* 规则:能散不连
|
|
77
|
+
*
|
|
78
|
+
* 尽量让字根不相连。该规则采集自五笔
|
|
79
|
+
*/
|
|
80
|
+
export declare const attaching: Sieve<number>;
|
|
81
|
+
/**
|
|
82
|
+
* 规则:同向笔画
|
|
83
|
+
*
|
|
84
|
+
* 尽量让方向相同的笔画包含在同一个字根里。该规则采集自五笔
|
|
85
|
+
*/
|
|
86
|
+
export declare const orientation: Sieve<number>;
|
|
87
|
+
/**
|
|
88
|
+
* 规则:结构完整
|
|
89
|
+
*
|
|
90
|
+
* 避免框类部件被拆散。该规则采集自宇浩输入法的文档
|
|
91
|
+
*
|
|
92
|
+
* @see https://zhuyuhao.com/yuhao/docs/learn.html#结构完整
|
|
93
|
+
*/
|
|
94
|
+
export declare const integrity: Sieve<number>;
|
|
95
|
+
export declare const sieveMap: Map<SieveName, Sieve<number> | Sieve<number[]>>;
|
|
96
|
+
/**
|
|
97
|
+
* 选择最优的拆分方案
|
|
98
|
+
*
|
|
99
|
+
* @param config - 配置
|
|
100
|
+
* @param component - 待拆分部件
|
|
101
|
+
* @param schemeList - 拆分方案列表
|
|
102
|
+
* @param rootMap - 字根映射,从切片的二进制表示到字根名称的映射
|
|
103
|
+
*/
|
|
104
|
+
export declare const select: (config: AnalysisConfig, component: ComputedComponent, schemeList: Scheme[], rootMap: Map<number, string>, requiredRoots: Set<number>) => NoSchemeError | {
|
|
105
|
+
scheme: Scheme;
|
|
106
|
+
roots: string[];
|
|
107
|
+
evaluation: Map<SieveName, number | number[]>;
|
|
108
|
+
optional: boolean;
|
|
109
|
+
}[];
|
|
110
|
+
export {};
|