Regex of multiple of n

codewars 做 Kata 時遇到 Regular Expression - Check if divisible by 0b111 (7) 這題。因為在正規和計算理論都有碰到 DFA ,想說當作複習,找出對所有 n 最一般的 DFA ,然後試著直接得到對應的正則表達式

分析

假設到某個時段吃進來的字串為 k 的二進位表示,若接下來的符號是 1k 就會變成 2k + 1;如果是 0k 則會變成 2k

A k k 2k+1 2k+1 k->2k+1 1 2k 2k k->2k 0

也就是說只需要紀錄 0 ≤ k ≤ n − 1 接上 10 會走到哪裡,就能夠將 DFA 建出來。

範例

以下是 n = 4 的例子。藉由上述的作法我們能得到所有的轉換為

$$ \begin{aligned} 0 \xrightarrow{0} 0 \quad 0 \xrightarrow{1} 1 \quad 1 \xrightarrow{0} 2 \quad 1 \xrightarrow{1} 3 \\ 2 \xrightarrow{0} 0 \quad 2 \xrightarrow{1} 1 \quad 3 \xrightarrow{0} 2 \quad 3 \xrightarrow{1} 3 \end{aligned} $$

畫成圖就能得到下面的 DFA。

A p 0 0 p->0 0->0 0 1 1 0->1 1 2 2 1->2 0 3 3 1->3 1 2->0 0 2->1 1 3->2 0 3->3 1

DFA to regex

假設要拿掉節點 k ,就必須補上 I × O 的邊,這邊的 I 為連到 k 的節點; Ok 連到的節點。

因為不太了解拿掉的順序對結果的影響,就先從大到小一個個拿掉。

A p 0 0 p->0 0->0 0 1 1 0->1 1 2 2 1->2 0|11*0 2->0 0 2->1 1 A p 0 0 p->0 0->0 0|1((0|11*0)1)*(0|11*0)0

最後得到辨認 5 的倍數的二進位表示的正則表達式為

(0|1((0|11*0)1)*(0|11*0)0)*

其他想法

觀察上面的範例可以發現只要將有 self-loop 的節點拿掉,那個迴圈就會被分到新產生或更新的邊上,造成表達式出現重複的式子,所以先拿掉 self-loop 的節點應該能減少表達式的長度(?)。