為什麼 Telegram 不能搜尋中文訊息

由於 LINE 大幅調漲其官方帳號推送訊息的價格,許多具有重要影響力的團體及個人逐漸從 LINE 轉移至 Telegram,也讓這個在台灣原屬小眾的通訊軟體浮出水面,進入大眾的視野。

然而,Telegram 對於中文的支持並不甚良好,尤其搜尋功能遇上漢字即與殘廢無異。所以,原因究竟是什麼呢??讓我們來逐步抽絲剝繭吧

首先,我們看看 Telegram 核心 td-lib 是如何儲存訊息的。這是 MessagesDb.cpp,從 12 到 14 行我們可以知道 Telegram 使用 SQLite 作為資料庫:

#include "td/db/SqliteConnectionSafe.h"
#include "td/db/SqliteDb.h"
#include "td/db/SqliteStatement.h"

接著,我們可以發現這個 database 中有不少 table,但是我們只關心 messages_fts 這個 virtual table。那麼,這個 fts 又是什麼呢??

CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(text, content='messages', content_rowid='search_id', tokenize = "unicode61 remove_diacritics 0 tokenchars '\a'")

原來,fts 其實是 full-text search,也就是所謂的全文搜索,而 fts5 則是 SQLite 內建可以提供全文搜索的模組。

說到這裡,先來打個岔談談字串搜尋。在高中演算法競賽中,除了樸素的暴力解外,最常見而廣為人知的方法有兩種:KMP 演算法與其他基於 hash 的演算法。前者是由資訊界史詩級教授 Donald E. Knuth 及 Pratt, Morris 於 1977 提出。

而 SQLite fts5 比較類似基於 hash 的演算法,他的做法大約如下:

  1. 將字串以 tokenizer 切割成一段段的 phrase
  2. phrase 進行 hash 後存進 table
  3. 搜尋時也將搜尋字串以 tokenizer 切割成一段段的 phrase,在 hash table 中進行搜尋

舉例而言,訊息 qwert qaz qsc 在搜尋時只會分別匹配到 qwertqazqsc 這三個字串,你搜尋單獨的 qaer 都不會匹配到。

訊息內容
搜尋一個 phase,成功
搜尋單獨 q,反而失敗

那問題接著來了:tokenizer 根據什麼切割??用以切割 phase 的字元,我們稱之 separator or delimiter,而剩下構成 phase 的字元稱之 token。進一步來說,依據 unicode 的類別,屬於 L*(letter)N*(number)Co(other) 預設為 token,其餘都被視為 separator

所以我們終於找到 Telegram 不能搜尋中文訊息的根本原因了。原來,由於 unicode CJK (中日韓表意文字)絕大部分皆屬於 Lo 類別,也就是說,漢字語漢字間沒有任何 separator,會被整串拿去 hash,從而導致搜尋無效。

搜尋整串 CJK 即成功,證實我們的推論

結語

不小心打了一堆廢話呢,相信大家都知道 Telegram 不能搜尋中文訊息的原因了。解決的方法我有想到兩種,其一是手動在漢字間插入不可見的 separator,另一種是寫個客製化的 tokenizer

於此將我的一點淺見拿來分享作為拋磚引玉,希望各位電神不吝指教。未來倘若有空我也十分希望能為 Telegram 社群做出貢獻。

熱愛程式的高中生。https://kevinweng.tk/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store