Skip to content

Commit

Permalink
Update tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
forFudan committed Nov 27, 2023
1 parent 58a058b commit 5409ab0
Showing 1 changed file with 133 additions and 5 deletions.
138 changes: 133 additions & 5 deletions docs/learn.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,9 @@ nav_order: 12
>
>以上优先级,还可以高度归纳为:**少顺整散连交断大**
取根时,如果有多种不同的拆分方式,则按**优先级从高到低**依次检查以上规则,选取符合条件的,排除不满足的,最终得到唯一的拆分方案。以下为规则详细介绍:
取根时,如果有多种不同的拆分方式,则按**优先级从高到低**依次检查以上规则,选取符合条件的,排除不满足的,最终得到唯一的拆分方案。因此,某种意义上来说,宇浩输入法的拆分规则,其实是一种「比较和淘汰」的排除法,[后文中会展示这些步骤的机器实现](#机器实现)

以下为规则详细介绍:

{: .note}
在进行拆分前,首先需要「确认」某个字根到底存不存在,是否合规,也就是检查字根的「内在属性」。比如「土」「士」,两横的长度直接决定了字根的异同,再比如「王」中间如果被笔画「穿心」,则不再是「王」。核心规则有三条:散件不分割、竖向不包夹、横间不穿心。这部分的讨论作为进阶内容,于此处跳过。若有兴趣,可以在后续章节阅读。
Expand Down Expand Up @@ -404,10 +406,10 @@ nav_order: 12
字根取大,指的是按照笔顺拆字时:

1. 让完全符合笔顺的部件尽可能地大。也就是说,只要其中某个字根多写一笔仍然符合笔顺,就多写一笔。
2. 让首笔靠前的字根尽可能地大
3. 让非歪斜根尽可能地大
2. 让非歪斜根尽可能地大
3. 让首笔靠前的字根尽可能地大

「取大原则」,按定义,是一个兜底原则。它保证了最终只有一个候选方案能够胜出。一般而言,上述两条中的第二条比较常见
「取大原则」,按定义,是一个兜底原则。它保证了最终只有一个候选方案能够胜出。一般而言,上述两条中的第三条比较常见

{: .example }
>比如:
Expand All @@ -426,7 +428,7 @@ nav_order: 12
>- 结构合理都不适用。
>- 我们最后检查「字根取大」规则。我们发现,两个字根是穿插书写的。根据规则,只要其中某个字根多写一笔仍然符合笔顺,就多写一笔。「十彐女」可以保证合笔顺的字根写了三笔。而「龶乛女」中,符合笔顺的字根只有一笔。因此「十彐女」胜出。
第三条只有一种场合会出现,那就是部分字根有竖变撇的附属根。如:「千」字根的「丨」变成「丿」,「干」字根的「丨」变成「丿」,称为「歪斜根」。
第二条只有一种场合会出现,那就是部分字根有竖变撇的附属根。如:「千」字根的「丨」变成「丿」,「干」字根的「丨」变成「丿」,称为「歪斜根」。

{: .example }
>比如:
Expand All @@ -435,6 +437,132 @@ nav_order: 12
>- 「井」拆「二{介下}」而不拆「キ丨」,因为因为「キ」的一竖是撇。故而对{介下}取大。
>- 「缓」拆「纟爪干又」而不拆「纟爪二夂」。虽然前者「干」的竖是撇,但后者出现了「字根相交」。根据规则优先级,「字根相交」低于「字根相连」,故而拆为「纟爪干又」。
### 机器实现
<!-- {: .no_toc } -->

这里释出宇浩拆分规则的伪代码,用以展示使用计算机程序来实现拆分筛选的算法逻辑。注意,宇浩的拆分规则是**淘汰性**而非**引导性**的,故而算法的前置条件是已经拥有了若干的候选拆分,在此基础上,程序可以筛选出最满足宇浩拆分规则的唯一拆分。

假设某个汉字存在`N`个候选拆分,记为`divs = {div_i | i in [1, 2, ..., N]}`。每个拆分都是字根的**有序**集合,记为`div = {root_i | i in [1, 2, ..., M]}`,有以下属性:

- div.len: int 字根个数
- div.bishun: Booleann 字根是否完全合笔顺
- div.san: Boolean 字根是否分散
- div.lian: Boolean 字根是否相连
- div.jiao: Boolean 字根是否相交
- div.duan: Boolean 字根是否断开

注意:div.san, div.lian, div.jiao, div,duan 中有且只有一个是 True。

以下伪代码会从`N`个候选拆分`divs = {div_i | i in [1, 2, ..., N]}`中选出最优拆分:

```python
# 字根最少
for div_i in divs:
# 移除字根数量不是最少的候选拆分
if div_i.len != min([i.len for i in divs]):
divs.remove(div_i)
if len(divs) == 1:
break # 剩下唯一拆分候选,则停止判断

# 符合笔顺
for div_i in divs:
# 存在完全符合笔顺的拆分时,移除不完全符合笔顺的候选拆分
if div_i.bishun != max([i.bishun for i in divs]):
divs.remove(div_i)
if len(divs) == 1:
break # 剩下唯一拆分候选,则停止判断

# 结构完整
root_completeness_score = {
囗: 2,
日: 2,
目: 2,
田: 2,
冂: 1,
勹: 1,
匚: 1,
凵: 1,
コ: 1,
其他: 0,
} # 包围结构得分更高
for div_i, div_j in permutation(divs, 2): # 俩俩取候选拆分比较
common = intersect(div_i, div_j) # 共同的拆分部分
unique_i = div_i.difference(common) # 非共同的拆分部分
unique_j = div_j.difference(common) # 非共同的拆分部分
# 非共同字根中有包围结构的保留
if unique_i.map(root_completeness_score) < unique_j.map(root_completeness_score):
divs.remove(div_i)
if len(divs) == 1:
break # 剩下唯一拆分候选,则停止判断

# 字根分散
for div_i in divs:
# 存在散的拆分时,移除不散的候选拆分
if div_i.san != max([i.san for i in divs]):
divs.remove(div_i)
if len(divs) == 1:
break # 剩下唯一拆分候选,则停止判断

# 字根相连
for div_i in divs:
# 存在连的拆分时,移除不连的候选拆分
if div_i.lian != max([i.lian for i in divs]):
divs.remove(div_i)
if len(divs) == 1:
break # 剩下唯一拆分候选,则停止判断

# 字根相交
for div_i in divs:
# 存在交的拆分时,移除不交的候选拆分
if div_i.jiao != max([i.jiao for i in divs]):
divs.remove(div_i)
if len(divs) == 1:
break # 剩下唯一拆分候选,则停止判断

# 字根断开
# 断属性为散连交的补集,故不用再进行判断

# 字根取大之一
# 让完全符合笔顺的部件尽可能地大
for div_i, div_j in permutation(divs, 2): # 俩俩取候选拆分比较
common = intersect(div_i, div_j) # 共同的拆分部分
unique_i = div_i.difference(common) # 非共同的拆分部分
unique_j = div_j.difference(common) # 非共同的拆分部分
# root_bishun_score(div) 是局部拆分中完全符合笔顺的字根数量
# 非共同字根中,完全符合笔顺的字根数量多的保留
if root_bishun_score(unique_i) < root_bishun_score(unique_j):
divs.remove(div_i)
if len(divs) == 1:
break # 剩下唯一拆分候选,则停止判断

# 字根取大之二
# 让非歪斜根尽可能地大
for div_i, div_j in permutation(divs, 2): # 俩俩取候选拆分比较
common = intersect(div_i, div_j) # 共同的拆分部分
unique_i = div_i.difference(common) # 非共同的拆分部分
unique_j = div_j.difference(common) # 非共同的拆分部分
# leaning_root_strokes(div) 是局部拆分中歪斜根的笔画数
# 非共同字根中,非歪斜根笔画多的保留
if leaning_root_strokes(unique_i) < leaning_root_strokes(unique_j):
divs.remove(div_i)
if len(divs) == 1:
break # 剩下唯一拆分候选,则停止判断

# 字根取大之三
# 让首笔靠前的字根尽可能地大
for div_i, div_j in permutation(divs, 2): # 俩俩取候选拆分比较
common = intersect(div_i, div_j) # 共同的拆分部分
unique_i = div_i.difference(common) # 非共同的拆分部分
unique_j = div_j.difference(common) # 非共同的拆分部分
# root_i.strokes.len 是字根的笔画数
# 非共同字根中,靠前的字根笔画多的保留
for root_i, root_j in zip(unique_i, unique_j): # 俩俩取字根比较
if root_i.strokes.len < root_j.strokes.len:
divs.remove(div_i)

# 到此,必然只剩下唯一的拆分候选,即为最终拆分
```

### 拆字举例
<!-- {: .no_toc } -->

Expand Down

0 comments on commit 5409ab0

Please sign in to comment.