9

我正在自学正则表达式,在网上发现了一个有趣的练习题,涉及编写一个正则表达式来识别所有可被 3 整除的二进制数(并且只识别这样的数字)。老实说,这个问题要求为这种场景构建一个 DFA,但我认为使用正则表达式应该是等效的。

我知道有一个小规则可以确定二进制数是否可以被 3 整除:取数字中偶数位的个数,然后减去数字中奇数位的个数 - 如果这等于零,该数字可被 3 整除(例如:110 - 1 在偶数 2 插槽中,1 在奇数 1 插槽中)。但是,我在将其调整为正则表达式时遇到了一些麻烦。

我最接近的是意识到这个数字可以是 0,所以这将是第一个状态。我还看到所有可被 3 整除的二进制数都以 1 开头,所以这将是第二种状态,但我从那里被困住了。有人可以帮忙吗?

4

4 回答 4

11

按照 Oli Charlesworth 所说,您可以构建 DFA 以使基数b除以某个除数d,其中 DFA 中的状态代表除法的其余部分。

对于您的情况(基数 2 - 二进制数,除数d= 3 10):

初始 DFA

请注意,上面的 DFA 接受空字符串作为可被 3 整除的“数字”。这可以通过在前面添加一个中间状态来轻松解决:

固定 DFA

转换为理论正则表达式可以用正常的过程来完成。

当您获得 DFA 后,可以轻松地转换为支持递归正则表达式的风格中的实用正则表达式。在CodeGolf.SE的这个问题中显示了 (base b= 10, d= 7 10 ) 的情况。

让我在 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>)

分解它,你可以看到它是如何构造的。原子分组(或非回溯组,或具有所有格行为的组)用于确保仅匹配空字符串替代项。(?DEFINE)这是在 Perl中模拟的一个技巧。然后,当数字除以 7 时,组对应于 0 到 6 的余数AG

(?!$)
(?>
  (|(?<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>)
于 2013-03-11T06:44:21.267 回答
5

我有另一种方法来解决这个问题,我认为这更容易理解。当我们将一个数除以 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 来描述二进制数: DFA 描述可被 3 整除的二进制数

注意:边缘q2 -> q1应标记为 0。

于 2015-11-06T20:45:53.217 回答
2

能被 3 整除的二进制数分为 3 类:

  1. 具有两个连续 1 或两个 1 由偶数个 0 分隔的数字。有效地每一对“取消”自己。

(例如 11、110、1100、1001、10010、1111)

(十进制:3、6、12、9、18、15)

  1. 三个 1 的数字,每个数字由奇数个 0 分隔。这些三胞胎也“取消”了自己。

(例如 10101、101010、1010001、1000101)

(十进制:21、42、81、69)

  1. 前两个规则的某种组合(包括彼此内部)

(例如 1010111、1110101、1011100110001)

(十进制:87、117、5937)

因此,考虑到这三个规则的正则表达式很简单:

0*(1(00)*10*|10(00)*1(00)*(11)*0(00)*10*)*0*

如何阅读:

() 封装

* 表示前一个号码/组是可选的

| 表示括号内任一侧的选项选择

于 2016-11-10T02:55:17.017 回答
1

您遇到的问题是,虽然您的技巧(可能)有效,但它并没有映射到实际的 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]。这个描述是不是很熟悉?

于 2013-03-11T02:12:41.217 回答