可被 3 整除的二进制数的正则表达式
Posted
技术标签:
【中文标题】可被 3 整除的二进制数的正则表达式【英文标题】:Regular Expression for Binary Numbers Divisible by 3 【发布时间】:2013-02-26 02:59:06 【问题描述】:我正在自学正则表达式,并在网上发现了一个有趣的练习题,其中涉及编写一个正则表达式来识别所有可被 3 整除的二进制数(并且仅限于此类数字)。老实说,这个问题要求为这种场景构建一个 DFA,但我认为使用正则表达式应该是等效的。
我知道有一个小规则可以确定二进制数是否可以被 3 整除:取数字中偶数位的个数,然后减去该位奇数位的个数 - 如果这等于 0,该数字可以被 3 整除(例如:110 - 1 在偶数 2 插槽中,1 在奇数 1 插槽中)。但是,我在将其调整为正则表达式时遇到了一些麻烦。
我最接近的是意识到数字可以是 0,所以这将是第一个状态。我还看到所有可被 3 整除的二进制数都以 1 开头,所以这将是第二种状态,但我从那里被困住了。有人可以帮忙吗?
【问题讨论】:
那么,你能画出刚才描述的 DFA 吗? @OliCharlesworth 不是真的,不。我最接近的是意识到这个数字可以是 0,所以这将是第一个状态。我还看到所有可被 3 整除的二进制数都以 1 开头,所以这将是第二种状态,但我从那里被困住了。 @Dan 我不明白相关性。 @JohnRoberts:确实。我认为那是因为它不能这样描述(假设你的把戏是正确的);它需要跟踪偶数和奇数之间的潜在任意差异,这反过来又需要任意数量的状态... @OliCharlesworth 同意。我认为鉴于我的伎俩,这是不可能的。我想知道是否还有其他方法。 【参考方案1】:按照 Oli Charlesworth 的说法,您可以构建 DFA 以将基数 b
数除以某个除数 d
,其中 DFA 中的状态代表除法的其余部分。
对于您的情况(基数 2 - 二进制数,除数 d
= 310):
请注意,上面的 DFA 接受空字符串作为可被 3 整除的“数字”。这可以通过在前面添加一个中间状态来轻松解决:
可以使用normal process 转换为理论正则表达式。
当您获得 DFA 后,可以轻松地转换为支持递归正则表达式的风格中的实用正则表达式。这在来自 CodeGolf.SE 的 this question 中的 (base b
= 10, d
= 710) 的情况下显示。
让我引用the regex in the answer by Lowjacker,用 Ruby 正则表达式风格编写:
(?!$)(?>(|(?<B>4\g<A>|5\g<B>|6\g<C>|[07]\g<D>|[18]\g<E>|[29]\g<F>|3\g<G>))(|(?<C>[18]\g<A>|[29]\g<B>|3\g<C>|4\g<D>|5\g<E>|6\g<F>|[07]\g<G>))(|(?<D>5\g<A>|6\g<B>|[07]\g<C>|[18]\g<D>|[29]\g<E>|3\g<F>|4\g<G>))(|(?<E>[29]\g<A>|3\g<B>|4\g<C>|5\g<D>|6\g<E>|[07]\g<F>|[18]\g<G>))(|(?<F>6\g<A>|[07]\g<B>|[18]\g<C>|[29]\g<D>|3\g<E>|4\g<F>|5\g<G>))(|(?<G>3\g<A>|4\g<B>|5\g<C>|6\g<D>|[07]\g<E>|[18]\g<F>|[29]\g<G>)))(?<A>$|[07]\g<A>|[18]\g<B>|[29]\g<C>|3\g<D>|4\g<E>|5\g<F>|6\g<G>)
分解它,你可以看到它是如何构造的。 atomic 分组(或 non-backtracking 组,或行为 possessively 的组)用于确保仅匹配空字符串替代项.这是在 Perl 中模拟 (?DEFINE)
的技巧。那么A
到G
这组数除以7时对应的余数是0到6。
(?!$)
(?>
(|(?<B>4 \g<A>|5 \g<B>|6 \g<C>|[07]\g<D>|[18]\g<E>|[29]\g<F>|3 \g<G>))
(|(?<C>[18]\g<A>|[29]\g<B>|3 \g<C>|4 \g<D>|5 \g<E>|6 \g<F>|[07]\g<G>))
(|(?<D>5 \g<A>|6 \g<B>|[07]\g<C>|[18]\g<D>|[29]\g<E>|3 \g<F>|4 \g<G>))
(|(?<E>[29]\g<A>|3 \g<B>|4 \g<C>|5 \g<D>|6 \g<E>|[07]\g<F>|[18]\g<G>))
(|(?<F>6 \g<A>|[07]\g<B>|[18]\g<C>|[29]\g<D>|3 \g<E>|4 \g<F>|5 \g<G>))
(|(?<G>3 \g<A>|4 \g<B>|5 \g<C>|6 \g<D>|[07]\g<E>|[18]\g<F>|[29]\g<G>))
)
(?<A>$| [07]\g<A>|[18]\g<B>|[29]\g<C>|3 \g<D>|4 \g<E>|5 \g<F>|6 \g<G>)
【讨论】:
【参考方案2】:我有另一种方法来解决这个问题,我认为这更容易理解。
当我们将一个数除以 3 时,我们可以得到三个余数:0、1、2。
我们可以使用表达式3t
(t
是自然数)来描述一个可被 3 整除的数。
当我们在余数为 0 的二进制数后面加 0 时,实际的十进制数将加倍。因为每个数字都在移动到更高的位置。
3t * 2 = 6t
,这个也能被3整除。
当我们在余数为 0 的二进制数后面加 1 时,实际的十进制数将加 1。
3t * 2 + 1 = 6t + 1
,余数为1。
当我们在余数为1的二进制数后面加1时,实际的十进制数将加一,余数为0;
(3t + 1)*2 + 1 = 6t + 3 = 3(2t + 1)
,可以被 3 整除。
当我们在余数为 1 的二进制数后添加 0 时。实际的十进制数将加倍。余数为 2。
(3t + 1)*2 = 6t + 2
.
当我们在余数为 2 的二进制数后添加 0 时,余数将为 1。
(3t + 2)*2 = 6t + 4 = 3(2t + 1) + 1
当我们在余数为 2 的二进制数后面加 1 时,余数仍为 2。
(3t + 2)*2 + 1 = 6t + 5 = 3(2t + 1) + 2.
余数为2的二进制数无论加多少1,余数永远为2。
(3(2t + 1) + 2)*2 + 1 = 3(4t + 2) + 5 = 3(4t + 3) + 2
所以我们可以用 DFA 来描述二进制数:
注意:边 q2 -> q1
应标记为 0。
【讨论】:
为什么 q2 状态有两个转换都标记为 1?我假设过渡到 q1 应该用 0 标记?【参考方案3】:能被 3 整除的二进制数分为 3 类:
-
具有两个连续 1 或两个 1 由偶数个 0 分隔的数字。有效地每一对“取消”自己。
(例如 11、110、1100、1001、10010、1111)
(十进制:3、6、12、9、18、15)
-
由三个 1 组成的数字,每个由奇数个 0 分隔。这些三胞胎也会“抵消”自己。
(例如 10101、101010、1010001、1000101)
(十进制:21、42、81、69)
-
前两条规则的某种组合(包括相互之间)
(例如 1010111、1110101、1011100110001)
(十进制:87、117、5937)
所以考虑到这三个规则的正则表达式很简单:
0*(1(00)*10*|10(00)*1(00)*(11)*0(00)*10*)*0*
如何阅读:
() 封装
* 表示前一个数字/组是可选的
|表示括号内任一侧的选项选择
【讨论】:
请更正您的正则表达式如下:0*(1(00)*10*|10(00)*1(00)*(1)*0(00)*10*)*0 *,因为在右边你可以制作你想要的循环【参考方案4】:您遇到的问题是,虽然您的技巧(可能)有效,但它并没有映射到实际的 DFA(您必须跟踪偶数和奇数之间的潜在任意差异,这需要任意数量的状态)。
另一种方法是注意(从 MSB 到 LSB)在 i
-th 字符 x[i]
之后,您的子字符串在模 3 算术中必须等于 0、1 或 2;将此值称为S[i]
。 x[i+1]
必须为 0 或 1,相当于乘以 2 并可选择加 1。
所以如果你知道S[i]
和x[i+1]
,你就可以计算出S[i+1]
。这个描述听起来很熟悉吗?
【讨论】:
以上是关于可被 3 整除的二进制数的正则表达式的主要内容,如果未能解决你的问题,请参考以下文章