可被 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) 的技巧。那么AG这组数除以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。 我们可以使用表达式3tt 是自然数)来描述一个可被 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 -&gt; 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 整除的二进制数的正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

十进制数的正则表达式(最多 3 个带逗号的十进制数)

判断二进制数能否被3或5整除

使用 PCRE 正则表达式匹配两个二进制数的正确加法

0 到 10 范围内任何正或负十进制数的正则表达式

非零和非十进制数的 JQuery 正则表达式验证

正则表达式--指定时间格式校验