From 86892593e7e84848577655669b5f458cc6c3c0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:45:21 +0900 Subject: [PATCH] =?UTF-8?q?optimize(data):=20=E7=BC=93=E5=AD=98=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 ++++++- codegen/preprocess/main.go | 4 +- coder_test.go | 6 +- data.go | 191 +++++++++++++++++++++---------------- 4 files changed, 148 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index a80aec5..cba9c6d 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ go run cmd/main.go -i -a 哦 -p o ```bash go run cmd/main.go -a 行 查询到汉字 行 的记录: -0) #149859999752449 行 [ɕiŋ阳平] 从行 xing xínɡ -1) #149859999554817 行 [xɑŋ阳平] 从行 hang háng +0) #149859999752449 行 [ɕ, iŋ, 阳平] 从行 xing xínɡ +1) #149859999554817 行 [x, ɑŋ, 阳平] 从行 hang háng 程序处理结束 ``` ### 编码 @@ -80,7 +80,7 @@ go run cmd/main.go -a 的 -p de -r 日 -re 🌞 已添加汉字: 的 读音: t, ɤ, 轻声 部首: 日 ID: 130309308023300 已添加部首: 日 颜文字: 🌞 查询到汉字 的 的记录: -0) #130309308023300 的 [tɤ轻声] 从日 de de +0) #130309308023300 的 [t, ɤ, 轻声] 从日 de de 程序处理结束 go run cmd/main.go -e 的 @@ -104,7 +104,14 @@ go run cmd/main.go -d "🥛👔⁨🐴👤🌼😺🐴👩,🏔️🌅 程序处理结束 ``` -## 实用工具 +## 作为库引用 +### 特别注意 +本`package`使用了自修改的`modernc.org/sqlite`数据库,如欲引入本包,需要在`go.mod`添加如下替换项。 +```bash +replace modernc.org/sqlite => github.com/fumiama/sqlite3 v1.20.0-with-win386 + +replace github.com/remyoudompheng/bigfft => github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b +``` ### 拼音识别拆分 将带声调的拼音拆分为以国际音标表示的声母韵母。 ```go @@ -114,3 +121,25 @@ if err != nil { } fmt.Println(s, y, tone) // tɕ i̯ʊŋ 上声 ``` +### 查汉字 +查一个汉字在数据库中的记录。 +```go +coder, err := emozi.NewCoder(time.Minute) +if err != nil { + panic(err) +} +defer coder.Close() +lst, err := coder.Lookup('行') +if err != nil { + panic(err) +} +fmt.Println("查询到汉字 行 的记录:") + for i, x := range lst { + fmt.Printf("%d)\t%s\n", i, x) +} +/* +查询到汉字 行 的记录: +0) #149859999752449 行 [ɕ, iŋ, 阳平] 从行 xing xínɡ +1) #149859999554817 行 [x, ɑŋ, 阳平] 从行 hang háng +*/ +``` diff --git a/codegen/preprocess/main.go b/codegen/preprocess/main.go index 247a24b..fd2df72 100644 --- a/codegen/preprocess/main.go +++ b/codegen/preprocess/main.go @@ -49,12 +49,12 @@ func main() { panic(fmt.Sprintf("ERROR: decoding data/%d.json: p: %s, f: %s", i, x.P, x.F)) } insert := func(w string) error { - err = c.AddChar(w, x.R, x.P, x.F) + _, _, err = c.AddChar(w, x.R, x.P, x.F) if err != nil { return fmt.Errorf("inserting table emozi of data/%d.json: %v", i, err) } for _, a := range x.A { - err = c.AddChar(w, x.R, "", a) + _, _, err = c.AddChar(w, x.R, "", a) if err != nil { return fmt.Errorf("inserting table emozi of data/%d.json, alter %s: %v", i, a, err) } diff --git a/coder_test.go b/coder_test.go index d1f0143..eb91e58 100644 --- a/coder_test.go +++ b/coder_test.go @@ -10,6 +10,7 @@ func TestEncode(t *testing.T) { if err != nil { t.Fatal(err) } + defer c.Close() es, lst, err := c.Encode(false, "你好,世界!看看多音字:行。") if err != nil { t.Fatal(err) @@ -30,11 +31,11 @@ func TestEncode(t *testing.T) { if len(lst) != 2 && lst[0] != 2 && lst[1] != 2 { t.Fail() } - es, _, err = c.Encode(false, "的") + es, _, err = c.Encode(false, "嗯") if err != nil { t.Fatal(err) } - if es.String() != "的🈳🈳🈳" { + if es.String() != "嗯🈳🈳🈳" { t.Fatal("got", es.String()) } } @@ -44,6 +45,7 @@ func TestDecode(t *testing.T) { if err != nil { t.Fatal(err) } + defer c.Close() s := "你好,世界!看看多音字:行。" es, _, err := c.Encode(false, s) if err != nil { diff --git a/data.go b/data.go index 8fe4095..d4b996a 100644 --- a/data.go +++ b/data.go @@ -50,9 +50,7 @@ func (z *字表) String() string { sb.WriteByte(' ') sb.WriteRune(z.W) sb.WriteString(" [") - sb.WriteString(z.S.String()) - sb.WriteString(z.Y.String()) - sb.WriteString(z.T.String()) + sb.WriteString(z.读音()) sb.WriteString("] 从") sb.WriteRune(z.R) sb.WriteByte(' ') @@ -62,6 +60,16 @@ func (z *字表) String() string { return sb.String() } +func (z *字表) 读音() string { + sb := strings.Builder{} + sb.WriteString(z.S.String()) + sb.WriteString(", ") + sb.WriteString(z.Y.String()) + sb.WriteString(", ") + sb.WriteString(z.T.String()) + return sb.String() +} + // CharGlobalID 计算全局唯一字表ID func CharGlobalID(w rune, f string) (int64, error) { p := 去调(f) @@ -177,28 +185,39 @@ func (c *Coder) 逆字(s 声母枚举, y 韵母枚举, t 声调枚举, r rune, l return rs, lstbuf, nil } -// AddChar 向主库添加一个新字 -// -// w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音 -func (c *Coder) AddChar(w, r, p, f string) error { +func (c *Coder) 添加字到表(table, w, r, p, f string) (int64, string, error) { if p == "" { p = 去调(f) } s, y, t, rw, rr, err := 拆音识字(w, r, p, f) if err != nil { - return err + return 0, "", err } - c.mu.Lock() - err = c.db.InsertUnique(主字表名, &字表{ - ID: 字表ID(rw, s, y, t), + id := 字表ID(rw, s, y, t) + revid := 逆字ID(s, y, t, rr) + x := &字表{ + ID: id, W: rw, S: s, Y: y, T: t, R: rr, P: p, F: f, - }) + } + c.mu.Lock() + err = c.db.InsertUnique(table, x) + if err == nil { + c.字表缓存[rw] = append(c.字表缓存[rw], *x) + c.逆字表缓存[revid] = append(c.逆字表缓存[revid], rw) + } c.mu.Unlock() if err != nil { - return errors.New("已有同音同形的字 '" + w + "'") + return 0, "", errors.New("已有同音同形的字 '" + w + "'") } - return nil + return id, x.读音(), nil +} + +// AddChar 向主库添加一个新字 +// +// w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音 +func (c *Coder) AddChar(w, r, p, f string) (int64, string, error) { + return c.添加字到表(主字表名, w, r, p, f) } // AddCharOverlay 向附加库添加一个新字, 覆盖在主库之上 @@ -206,66 +225,7 @@ func (c *Coder) AddChar(w, r, p, f string) error { // w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音 // 返回: 字表ID, 文字描述, error func (c *Coder) AddCharOverlay(w, r, p, f string) (int64, string, error) { - if p == "" { - p = 去调(f) - } - s, y, t, rw, rr, err := 拆音识字(w, r, p, f) - if err != nil { - return 0, "", err - } - return c.addcharoverlay(w, p, f, s, y, t, rw, rr) -} - -func (c *Coder) addcharoverlay(w, p, f string, s 声母枚举, y 韵母枚举, t 声调枚举, rw rune, rr rune) (int64, string, error) { - id := 字表ID(rw, s, y, t) - c.mu.Lock() - err := c.db.InsertUnique(附字表名, &字表{ - ID: id, - W: rw, S: s, Y: y, T: t, - R: rr, P: p, F: f, - }) - c.mu.Unlock() - if err != nil { - return 0, "", errors.New("已有同音同形的字 '" + w + "'") - } - sb := strings.Builder{} - sb.WriteString(s.String()) - sb.WriteString(", ") - sb.WriteString(y.String()) - sb.WriteString(", ") - sb.WriteString(t.String()) - return id, sb.String(), nil -} - -// ChangeCharOverlay 更改附加库的一项 -func (c *Coder) ChangeCharOverlay(oldw, oldr, oldf, neww, newr, newf string) (int64, string, error) { - s, y, t, rw, rr, err := 拆音识字(oldw, oldr, 去调(oldf), oldf) - if err != nil { - return 0, "", err - } - newp := 去调(newf) - ns, ny, nt, nrw, nrr, err := 拆音识字(neww, newr, newp, newf) - if err != nil { - return 0, "", err - } - q := "WHERE ID=" + strconv.FormatInt(字表ID(rw, s, y, t), 10) - x := 字表{} - c.mu.RLock() - err = c.db.Find(附字表名, &x, q) - c.mu.RUnlock() - if err != nil { - return 0, "", err - } - if x.R != rr { - return 0, "", errors.New("提供的旧部首 '" + string(rr) + "' 与记载的 '" + string(x.R) + "' 不符") - } - c.mu.Lock() - err = c.db.Del(附字表名, q) - c.mu.Unlock() - if err != nil { - return 0, "", err - } - return c.addcharoverlay(neww, newp, newf, ns, ny, nt, nrw, nrr) + return c.添加字到表(附字表名, w, r, p, f) } // StabilizeCharFromOverlay 将附加库中的一项固定到主库 @@ -285,30 +245,97 @@ func (c *Coder) StabilizeCharFromOverlay(id int64) (string, error) { return x.String(), c.db.Del(附字表名, q) } -// DelChar 删除主库的一个字 -func (c *Coder) DelChar(id int64) error { +// 清除缓存字 未加锁, 必须在写锁内调用 +func (c *Coder) 清除缓存字(x *字表) { + for i, ch := range c.字表缓存[x.W] { + if ch.ID == x.ID { + switch { + case i == 0: + c.字表缓存[x.W] = c.字表缓存[x.W][1:] + case i == len(c.字表缓存[x.W])-1: + c.字表缓存[x.W] = c.字表缓存[x.W][:i-1] + default: + c.字表缓存[x.W] = append(c.字表缓存[x.W][:i], c.字表缓存[x.W][i+1:]...) + } + break + } + } + revid := 逆字ID(x.S, x.Y, x.T, x.R) + for i, ch := range c.逆字表缓存[revid] { + if ch == x.W { + switch { + case i == 0: + c.逆字表缓存[revid] = c.逆字表缓存[revid][1:] + case i == len(c.逆字表缓存[revid])-1: + c.逆字表缓存[revid] = c.逆字表缓存[revid][:i-1] + default: + c.逆字表缓存[revid] = append(c.逆字表缓存[revid][:i], c.逆字表缓存[revid][i+1:]...) + } + break + } + } +} + +func (c *Coder) 删除表中字(table string, id int64) error { + q := "WHERE ID=" + strconv.FormatInt(id, 10) + x := 字表{} c.mu.Lock() defer c.mu.Unlock() - return c.db.Del(主字表名, "WHERE ID="+strconv.FormatInt(id, 10)) + err := c.db.Find(table, &x, q) + if err != nil { + return err + } + c.清除缓存字(&x) + return c.db.Del(table, q) +} + +// DelChar 删除主库的一个字 +func (c *Coder) DelChar(id int64) error { + return c.删除表中字(主字表名, id) } // DelCharOverlay 删除附加库的一个字 func (c *Coder) DelCharOverlay(id int64) error { - c.mu.Lock() - defer c.mu.Unlock() - return c.db.Del(附字表名, "WHERE ID="+strconv.FormatInt(id, 10)) + return c.删除表中字(附字表名, id) } // AddRadical 添加一个部首 func (c *Coder) AddRadical(r rune, e string) error { c.mu.Lock() defer c.mu.Unlock() - return c.db.InsertUnique(部首表名, &部首表{R: r, E: e}) + err := c.db.InsertUnique(部首表名, &部首表{R: r, E: e}) + if err == nil { + c.部首缓存[r] = e + if 无此字符(c.逆部首缓存[e], r) { + c.逆部首缓存[e] = append(c.逆部首缓存[e], r) + } + } + return err } // DelRadical 删除一个部首 func (c *Coder) DelRadical(r rune) error { + x := 部首表{} + q := "WHERE R=" + strconv.Itoa(int(r)) c.mu.Lock() defer c.mu.Unlock() - return c.db.Del(部首表名, "WHERE R="+strconv.Itoa(int(r))) + err := c.db.Find(部首表名, &x, q) + if err != nil { + return err + } + delete(c.部首缓存, r) + for i, item := range c.逆部首缓存[x.E] { + if item == r { + switch { + case i == 0: + c.逆部首缓存[x.E] = c.逆部首缓存[x.E][1:] + case i == len(c.逆部首缓存[x.E])-1: + c.逆部首缓存[x.E] = c.逆部首缓存[x.E][:i-1] + default: + c.逆部首缓存[x.E] = append(c.逆部首缓存[x.E][:i], c.逆部首缓存[x.E][i+1:]...) + } + break + } + } + return c.db.Del(部首表名, q) }