汉字帮助类——获取拼音与五笔简码

前言

系统各类的数据字典、业务数据检索,普遍是通过下拉框实现,部分会提供模糊检索,但是很少会提供通过五笔或拼音首码来检索的方式。

在过去参与的系统设计中,发现对于计算机系统不熟悉、键盘操作不熟练的用户来说,提供五笔或拼音首码检索的方式,可以大幅度提高表单录入的速度。

本文主要介绍如果通过汉字信息,获取拼音、五笔首码的信息。

字典数据的检索建议提供 ID、名称、代码、拼音首码、五笔首码 等方式,提升用户在表单录入时的录入速度。

准备

数据字典中的 ID、名称、代码 都是原生信息,很方便获取,但是 拼音首码 与 五笔首码 需要维护字典库存储用于生成数据字典。

目前已经通过 百度汉语 以及 汉典网 提取到基本汉字的信息,包括 拼音、五笔 等信息。

链接: https://pan.baidu.com/s/17OOspKdKk5MJi3YT3Nhsjw 密码: tgd1

帮助类

已经预先导出了一份包含 汉字、拼音首码、五笔首码 信息的文件。

链接: https://pan.baidu.com/s/14Z5waW6OIGo2_PMFf7cvow 密码: g37t

五笔与拼音首码获取帮助类,可以参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/// <summary>
/// 拼音五笔帮助类
/// </summary>
public static class PinyinWubiHelper
{
private static Dictionary<char, (char[] pinyin, char[] wubi)> _dicHanzi = new Dictionary<char, (char[] pinyin, char[] wubi)>();
static PinyinWubiHelper()
{
string text = Resource.chinese;
// 记录没有五笔或拼音的字符,存在需要抛出异常
List<char> listWubi = new List<char>();
List<char> listPinyin = new List<char>();
foreach (string hanzi in text.Split('\n', StringSplitOptions.RemoveEmptyEntries))
{
string[] info = hanzi.Split(',');
_dicHanzi.Add(info[0][0]
, (pinyin: info[2].Split('|', StringSplitOptions.RemoveEmptyEntries).Select(py => py[0]).ToArray()
, wubi: info[1].Split('|', StringSplitOptions.RemoveEmptyEntries).Select(wb => wb[0]).ToArray()));
if (_dicHanzi[info[0][0]].wubi.Length == 0) listWubi.Add(info[0][0]);
if (_dicHanzi[info[0][0]].pinyin.Length == 0) listPinyin.Add(info[0][0]);
}
if (listWubi.Count > 0 || listPinyin.Count > 0)
{
throw new Exception($"{(listWubi.Count > 0 ? $"存在无五笔首码的字符:{string.Concat(listWubi)}" : "")}\r\n{(listPinyin.Count > 0 ? $"存在无拼音首码的字符:{string.Concat(listPinyin)}" : "")}");
}
}

/// <summary>
/// 获取一段中文的五笔首码
/// </summary>
/// <param name="text">中文内容</param>
/// <param name="length">期望返回五笔首码的最大长度,默认 20</param>
/// <returns>处理后的五笔首码</returns>
public static IEnumerable<string> GetWubiCode(string text, int length = 20)
{
text = HandlingText(text, length);
if (!string.IsNullOrEmpty(text))
{
// 默认第一位首码时的内容并返回
var def = text
.ToCharArray()
.Select(ch => _dicHanzi.ContainsKey(ch) ? _dicHanzi[ch].wubi[0] : ch);

yield return new string(def.ToArray());

// 记录迭代到指定位置时还未返回的所有可能
List<List<char>> list = new List<List<char>> { new List<char>() };

// 循环字符串的所有字符
for (int i = 0; i < text.Length; i++)
{
if (_dicHanzi.ContainsKey(text[i]))
{
// 当前字符存在首码
char[] wubi = _dicHanzi[text[i]].wubi;
if (wubi.Length > 1)
{
// 1. 存在多个首码,需要将每种可能的结果返回,并且更新所有组合的可能性
// 获取当前位置之后的默认字符,用于返回
IEnumerable<char> last = def.Take(i + 1);

// 更新截止到当前字符之前的所有可能
List<List<char>> update = new List<List<char>>();

// 循环截止到当前字符之前的所有可能
for (int j = 0; j < list.Count; j++)
{
// 循环当前字符的所有首码
for (int k = 0; k < wubi.Length; k++)
{
var current = new List<char>(list[j]) { wubi[k] };

// 将截至到当前位置的所有默认可能返回
if (k > 0)
{
var result = new List<char>(current);
result.AddRange(last);
yield return new string(result.ToArray());
}

update.Add(current);
}
}

// 可能性增加,需要更新
list = update;
}
else
{
// 2. 只存在一个首码,将当前字符的首码添加到内容中
list.ForEach(l => l.Add(_dicHanzi[text[i]].wubi[0]));
}
}
else
{
// 当前字符不存在五笔首码,则将当前字符添加到内容中
list.ForEach(l => l.Add(text[i]));
}
}
}
}

/// <summary>
/// 获取一段中文的拼音首码
/// </summary>
/// <param name="text">中文内容</param>
/// <param name="length">期望返回拼音首码的最大长度,默认 20</param>
/// <returns>处理后的拼音首码</returns>
public static IEnumerable<string> GetPinyinCode(string text, int length = 20)
{
text = HandlingText(text, length);
if (!string.IsNullOrEmpty(text))
{
// 默认第一位首码时的内容并返回
var def = text
.ToCharArray()
.Select(ch => _dicHanzi.ContainsKey(ch) ? _dicHanzi[ch].pinyin[0] : ch);

yield return new string(def.ToArray());

// 记录迭代到指定位置时还未返回的所有可能
List<List<char>> list = new List<List<char>> { new List<char>() };

// 循环字符串的所有字符
for (int i = 0; i < text.Length; i++)
{
if (_dicHanzi.ContainsKey(text[i]))
{
// 当前字符存在首码
char[] pinyin = _dicHanzi[text[i]].pinyin;
if (pinyin.Length > 1)
{
// 1. 存在多个首码,需要将每种可能的结果返回,并且更新所有组合的可能性
// 获取当前位置之后的默认字符,用于返回
IEnumerable<char> last = def.Skip(i + 1);

// 更新截止到当前字符之前的所有可能
List<List<char>> update = new List<List<char>>();

// 循环截止到当前字符之前的所有可能
for (int j = 0; j < list.Count; j++)
{
// 循环当前字符的所有首码
for (int k = 0; k < pinyin.Length; k++)
{
var current = new List<char>(list[j]) { pinyin[k] };

// 将截至到当前位置的所有默认可能返回
if (k > 0)
{
var result = new List<char>(current);
result.AddRange(last);
yield return new string(result.ToArray());
}

update.Add(current);
}
}

// 可能性增加,需要更新
list = update;
}
else
{
// 2. 只存在一个首码,将当前字符的首码添加到内容中
list.ForEach(l => l.Add(_dicHanzi[text[i]].pinyin[0]));
}
}
else
{
// 当前字符不存在拼音首码,则将当前字符添加到内容中
list.ForEach(l => l.Add(text[i]));
}
}
}
}

// 特殊字符字典
private static Dictionary<char, char> _dicChar = new Dictionary<char, char>
{
['A'] = 'A',
['B'] = 'B',
['C'] = 'C',
['D'] = 'D',
['E'] = 'E',
['F'] = 'F',
['G'] = 'G',
['H'] = 'H',
['I'] = 'I',
['J'] = 'J',
['K'] = 'K',
['L'] = 'L',
['M'] = 'M',
['N'] = 'N',
['O'] = 'O',
['P'] = 'P',
['Q'] = 'Q',
['R'] = 'R',
['S'] = 'S',
['T'] = 'T',
['U'] = 'U',
['V'] = 'V',
['W'] = 'W',
['X'] = 'X',
['Y'] = 'Y',
['Z'] = 'Z',
['a'] = 'a',
['b'] = 'b',
['c'] = 'c',
['d'] = 'd',
['e'] = 'e',
['f'] = 'f',
['g'] = 'g',
['h'] = 'h',
['i'] = 'i',
['j'] = 'j',
['k'] = 'k',
['l'] = 'l',
['m'] = 'm',
['n'] = 'n',
['o'] = 'o',
['p'] = 'p',
['q'] = 'q',
['r'] = 'r',
['s'] = 's',
['t'] = 't',
['u'] = 'u',
['v'] = 'v',
['w'] = 'w',
['x'] = 'x',
['y'] = 'y',
['z'] = 'z',
['0'] = '0',
['1'] = '1',
['2'] = '2',
['3'] = '3',
['4'] = '4',
['5'] = '5',
['6'] = '6',
['7'] = '7',
['8'] = '8',
['9'] = '9',
};
// 用于替换内容,保留数字、小写字母、大写字母、基本汉字、〇、全角数字、全角小写字母、全角大写字母
private static readonly Regex _regex = new Regex(@"[^0-9A-Za-z\u4E00-\u9FA5\u30070-9a-zA-Z]");
private static string HandlingText(string text, int length = 20)
{
if (string.IsNullOrWhiteSpace(text)) return "";

// 将非数字字母汉字等移除
text = _regex.Replace(text, "");

List<char> content = new List<char>();
// 正则会移除高位字符,这里进行迭代已经不会出现高位字符被拆成两个特殊字符的情况
foreach (char ch in text)
{
// 将特殊字符替换,例如全角字符替换为半角
if (_dicChar.ContainsKey(ch))
{
content.Add(_dicChar[ch]);
}
else
{
content.Add(ch);
}

if (content.Count >= length) break;
}

return new string(content.ToArray()).ToUpper();
}
}

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
string text = "南京市长江大桥参观南京市长江大桥)!@$#AaBbCc123AaBbCc123";
Console.WriteLine($"[{text}]");
Console.WriteLine("拼音:");
foreach (var py in PinyinWubiHelper.GetPinyinCode(text, 50))
{
Console.WriteLine(py);
}
Console.WriteLine("五笔:");
foreach (var wb in PinyinWubiHelper.GetWubiCode(text, 50))
{
Console.WriteLine(wb);
}

// 控制台输出内容:
// [南京市长江大桥参观南京市长江大桥)!@$#AaBbCc123AaBbCc123]
// 拼音:
// NJSCJDQCGNJSCJDQAABBCC123AABBCC123
// NJSZJDQCGNJSCJDQAABBCC123AABBCC123
// NJSCJDQSGNJSCJDQAABBCC123AABBCC123
// NJSZJDQSGNJSCJDQAABBCC123AABBCC123
// NJSCJDQCGNJSZJDQAABBCC123AABBCC123
// NJSCJDQSGNJSZJDQAABBCC123AABBCC123
// NJSZJDQCGNJSZJDQAABBCC123AABBCC123
// NJSZJDQSGNJSZJDQAABBCC123AABBCC123
// 五笔:
// FYYTIDSCCFYYTIDSAABBCC123AABBCC123