用正則表達(dá)式匹配 3 的倍數(shù)

之前在知乎上看到了有人用正則表達(dá)式匹配 3 的倍數(shù),很驚訝,原來正則表達(dá)式還能這么用。先看下正則表達(dá)式:

^[0369]*(([147][0369]*|[258][0369]*[258][0369]*)([147][0369]*[258][0369]*)*([258][0369]*|[147][0369]*[147][0369]*)|[258][0369]*[147][0369]*)*$

是不是很難懂?知乎上有對這個表達(dá)式的簡要說明,不過比較簡略。本文將對該表達(dá)式的推導(dǎo)過程進(jìn)行詳細(xì)說明。

整除判斷

本文將介紹一種通用的方法,同樣后面闡述的方法也不局限于 3,而是以 3 為例,可以推廣到任意自然數(shù) n 的方法。

判斷一個數(shù)能否被 n 整除,可以轉(zhuǎn)換為判斷一個數(shù)被 n 除后的余數(shù)是否為 0。假設(shè)需要判斷的數(shù)是 m,那么有 m = i * n + p。假設(shè) m 是一個 k 位數(shù),則可寫為 m = 10^(k - 1) * a + 10^(k - 2) * b + 10^(k - 3) * c + … + 10 * g + h,其中 a、b、c、…、g、h ∈ {0,1,2,3,4,5,6,7,8,9},若定義 f(0) = 0,f(1) = af(2) = 10 * a + b,f(3) = 100 * a + 10 * b + c(f(x)的表達(dá)式不太好描述。。。),那么 f(2) mod n = (10 * a + b) mod n,根據(jù)模運算加法和乘法的性質(zhì),可知 f(2) mod n = (10 * a + b) mod n = (10 * a mod n + b mod n) mod n = (10 mod n * a mod n + b mod n) mod n=(10 mod n * f(1) mod n + b mod n),同理可得到一個遞推式:

f(k) mod n = (10 mod n * f(k - 1) mod n + h mod n) mod n

確定 DFA

由上面的分析我們可以知道:

  1. 一個數(shù)除以 n 的余數(shù)只有可能在 [0,n-1] 上;
  2. 一個數(shù)的前 k 位數(shù)除以 n 的余數(shù)可以通過這個數(shù)前 k - 1 位除以 n 的余數(shù)計算出來(遞推式)。

例如判斷 120 是否能被3 整除的過程如下:

1 mod 3 余數(shù)為 1
12 mod 3 余數(shù)為 (1 * 1 + 2) mod 3 = 0
120 mod 3 余數(shù)為 (1 * 0 + 0) mod 3 = 0,從而 120 能被 3 整除

由此可知,我們可以寫出被除數(shù)的每一位數(shù)字和被 n 除后余數(shù)之間的狀態(tài)轉(zhuǎn)換過程,每一個余數(shù)是一種狀態(tài),而每一個數(shù)字是轉(zhuǎn)換的一條邊。那么對于 n = 3 來說,余數(shù)有 0、1、2 三種狀態(tài),輸入有 0、1、2、…、9 十種。

下面用表格列出所有狀態(tài):

上一個狀態(tài)(余數(shù)) 輸入(0-9) 下一個狀態(tài)
0 0 (1*0+0) mod 3=0
1 0 (1*1+0) mod 3=1
2 0 (1*2+0) mod 3=2
0 1 (1*0+1) mod 3=1
1 1 (1*1+1) mod 3=2
2 1 (1*2+1) mod 3=0
0 2 (1*0+2) mod 3=2
1 2 (1*1+2) mod 3=0
2 2 (1*2+2) mod 3=1
0 3 (1*0+3) mod 3=0
1 3 (1*1+3) mod 3=1
2 3 (1*2+3) mod 3=2
0 4 (1*0+4) mod 3=1
1 4 (1*1+4) mod 3=2
2 4 (1*2+4) mod 3=0
0 5 (1*0+5) mod 3=2
1 5 (1*1+5) mod 3=0
2 5 (1*2+5) mod 3=1
0 6 (1*0+6) mod 3=0
1 6 (1*1+6) mod 3=1
2 6 (1*2+6) mod 3=2
0 7 (1*0+7) mod 3=1
1 7 (1*1+7) mod 3=2
2 7 (1*2+7) mod 3=0
0 8 (1*0+8) mod 3=2
1 8 (1*1+8) mod 3=0
2 8 (1*2+8) mod 3=1
0 9 (1*0+9) mod 3=0
1 9 (1*1+9) mod 3=1
2 9 (1*2+9) mod 3=2

由此可見:0—[0,3,6,9]—>0,0—[1,4,7]—>10—[2,5,8]—>2,1—[2,5,8]—>0,1—[0,3,6,9]—>1,1—[1,4,7]—>2,2—[1,4,7]—>02—[2,5,8]—>1,2—[0,3,6,9]—>2。用 DFA 表示如下:

DFA

DFA 轉(zhuǎn)正規(guī)文法

為了區(qū)分,我們標(biāo)記 A 為終止到狀態(tài)0的字符串集合,B C類似,那么可以列出三個方程:

A = A[0369]|B[258]|C[147]|ε
B = A[147]|B[0369]|C[258]
C = A[258]|B[147]|C[0369]

由此可知,這是左線性文法,易推導(dǎo)出產(chǎn)生式:

L -> LU|V

消除左遞歸,推導(dǎo)得:L -> VU*

因此上面方程可以改寫為:

A = (ε|B[258]|C[147])[0369]*   (1)
B = (A[147]|C[258])[0369]*   (2)
C = (A[258]|B[147])[0369]*   (3)

將(3)代入(1)(2)得

A = (ε|B[258]|(A[258]|B[147])[0369]*[147])[0369]*  (4)
B = (A[147]|(A[258]|B[147])[0369]*[258])[0369]*   (5)

用分配律展開(5)中的豎線得到

B = A[147][0369]*|A[258][0369]*[258][0369]*|B[147][0369]*[258][0369]*

B = A[147][0369]*([147][0369]*[258][0369]*)*|A[258][0369]*[258][0369]*([147][0369]*[258][0369]*)*

把它代入(4)得

A = (ε|B[258]|(A[258]|B[147])[0369]*[147])[0369]*
= [0369]*|B[258][0369]*|A[258][0369]*[147][0369]*|B[147][0369]*[147][0369]*
= [0369]*|A[147][0369]*([147][0369]*[258][0369]*)*[258][0369]*|A[258][0369]*[258][0369]*([147][0369]*[258][0369]*)*[258][0369]*|A[258][0369]*[147][0369]*|A[147][0369]*([147][0369]*[258][0369]*)*[147][0369]*[147][0369]*|A[258][0369]*[258][0369]*([147][0369]*[258][0369]*)*[147][0369]*[147][0369]*
= [0369]*([147][0369]*([147][0369]*[258][0369]*)*[258][0369]*|[258][0369]*[258][0369]*([147][0369]*[258][0369]*)*[258][0369]*|[147][0369]*([147][0369]*[258][0369]*)*[147][0369]*[147][0369]*|[258][0369]*[258][0369]*([147][0369]*[258][0369]*)*[147][0369]*[147][0369]*|[258][0369]*[147][0369]*)*
= [0369]*(([147][0369]*|[258][0369]*[258][0369]*)([147][0369]*[258][0369]*)*([258][0369]*|[147][0369]*[147][0369]*)|[258][0369]*[147][0369]*)*

對結(jié)果加上開始結(jié)束符^$,即完成了對3的倍數(shù)正則表達(dá)式的推導(dǎo)。

驗證

用 JavaScript 寫一段程序測試運行結(jié)果,代碼如下:

const regexp = /^[0369]*(([147][0369]*|[258][0369]*[258][0369]*)([147][0369]*[258][0369]*)*([258][0369]*|[147][0369]*[147][0369]*)|[258][0369]*[147][0369]*)*$/;
console.log(regexp.test(120));
console.log(regexp.test(112233));
console.log(regexp.test(1024));

運行結(jié)果如下,說明能正確匹配出結(jié)果。

運行結(jié)果

參考文獻(xiàn)

  1. https://www.zhihu.com/question/24824487/answer/29109747
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容