一種簡單的字元轉義演算法

有的時候我們希望將字元串中的某些特殊字元替換成其他字元,來方便我們對於分隔符的使用,比如說我們需要轉義字元串中的小數點字元(.),來將字元串拼成用點號隔開的欄位,來形成KVDB中的一個key;或者我們需要轉義字元串中的/、?、*來形成一個文件名(當然,真正的文件名需要轉義的字元可能會更多)。除了採用urlencode、base64以及各種各樣的現成的escape方法以外,有一種簡單的方法可以快速寫出這樣的代碼,很適合轉義少量字元,而基本不破壞整個字元串的可讀性,代碼也只有一行。

假設我們要轉移一些字元c_0, c_1, c_2, c_3, ...c_n,我們可以採用這樣的方法:

  1. 挑選一個不在要轉移字元當中的新字元e。一般來說我比較喜歡的字元包括+和$,可以按情況選擇。盡量選擇出現概率低的符號,盡量不要選擇數字和字母(你不會喜歡一個單詞或者數字中間跑出來符號這種事情)
  2. e, c_0, c_1, ..., c_n依次替換成提前選定的e開頭的字元串。這些字元串應該符合如下的規則:
    1. 除了最後一個c_n對應的字元串以外,其他字元串不能包含e。
    2. 不能為另一個字元串的前綴
    3. 不能包含c_1, c_2, ..., c_n

在解轉義的時候,只需要:

c_n, c_{n-1}, ..., c_2, c_1, e的順序,將相應的字元串重新替換回原來的字元即可。

比如說我們現在想要轉義小數點(.),冒號(:),斜杠(/)三個字元,我們選中$作為字元e,然後分別將四個字元替換成$_, $+, $-和$$:

def escape_key(k):n return k.replace($, $_).replace(., $+)n .replace(:, $-).replace(/, $$)nndef unescape_key(k):n return k.replace($$, /).replace($-, :)n .replace($+, .).replace($_, $)n

證明正確性:

首先,這個編碼方式形成了一種前綴碼:不以e開頭的字元都表示自己;e開頭的相應的字元串表示相應的字元。用這種方法編碼後的字元串是無歧義的。由於e開頭相應字元串中沒有包含要轉移的字元,因此我們所有替換的字元都是原字元串中的字元,得到的結果與前綴編碼方法得到的結果相同。更進一步,將e, c_1, c_2, ..., c_k全部替換成相應字元串,後面字元暫不轉移的編碼方式也構成了前綴碼,因此我們每一步替換都得到一個前綴碼編碼的字元串,它都是無歧義的。

接下來考慮解碼的過程,對於每一個c_k,在編碼時對它進行替換之前,由於所有字元e開頭的字元串都是一個e開頭的轉義字元串,這些字元串不是c_k對應字元串的前綴,c_k對應字元串也不是他們的前綴,因此原串中不存在c_k對應字元串,替換後,從前往後匹配到的所有字元串都代表c_k。因此解碼的時候,將這個字元串重新替換回c_k,就可以回到c_k被替換掉之前的狀態。

當所有的c_k都被替換回去之後,字元串中所有出現的字元e都一定是以e的轉義字元串的形式出現的,一一對應,因此將這個字元串替換回e就可以回到原來的字元串了。

例如前面的例子中:

>>> escape_key(a/b.c:def/ghi::jkl////.:/..mn)na$$b$+c$-def$$ghi$-$-jkl$$$$$$$$$+$-$$$+$+mnn>>> unescape_key(_)na/b.c:def/ghi::jkl////.:/..mnn

推薦閱讀:

全球第一款開源線下搜索引擎OpenGenus,線下搜索代碼、演算法
自己做個AlphaGo(一)(井字棋)
演算法正在形成新型生態系統,它將生生不息,次元書摘@《終極演算法》

TAG:算法 | 编程 | Python |