[英]Regular Expression for Binary Numbers Divisible by 3
我在自学正则表达式,在网上发现了一个有趣的练习题,就是写一个正则表达式来识别所有可以被 3 整除的二进制数(并且只有这样的数)。 老实说,问题要求为这种情况构建 DFA,但我认为使用正则表达式应该等效。
我知道有一个小规则来确定一个二进制数是否可以被 3 整除:取数字中偶数位的 1 数,然后减去数字中奇数位的 1 数 - 如果这等于零,该数字可被 3 整除(例如:110 - 1 在偶数 2 插槽中,1 在奇数 1 插槽中)。 但是,我在将其调整为正则表达式时遇到了一些麻烦。
我最接近的是意识到数字可以是 0,所以这将是第一个状态。 我还看到所有可被 3 整除的二进制数都以 1 开头,因此这将是第二种状态,但我被困在那里。 有人可以帮忙吗?
按照 Oli Charlesworth 所说的,您可以构建 DFA 以将基数b
数除以某个除数d
,其中 DFA 中的状态代表除法的余数。
对于您的情况(基数 2 - 二进制数,除数d
= 3 10 ):
请注意,上面的 DFA 接受空字符串作为可被 3 整除的“数字”。这可以通过在前面再添加一个中间状态来轻松解决:
转换为理论正则表达式可以用正常的过程完成。
获得 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>)
分解它,您可以看到它是如何构建的。 原子分组(或非回溯组,或具有所有格行为的组)用于确保仅匹配空字符串替代项。 这是在 Perl 中模拟(?DEFINE)
的技巧。 那么当数字除以7时,组A
到G
对应于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>)
我有另一种方法来解决这个问题,我认为这更容易理解。 当我们将一个数除以 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。
可被 3 整除的二进制数分为 3 类:
(例如 11, 110, 1100,1001,10010, 1111)
(十进制:3、6、12、9、18、15)
(例如 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*
如何阅读:
() 封装
* 表示前一个号码/组是可选的
| 表示括号内任一侧的选项选择
您遇到的问题是,虽然您的技巧(可能)是有效的,但它并没有映射到实际的 DFA(您必须跟踪偶数和奇数之间的潜在任意差异,这需要任意数字状态)。
另一种方法是注意(从 MSB 到 LSB)在第i
个字符x[i]
,您的子字符串在模 3 算术中必须等于 0、1 或 2; 将此值称为S[i]
。 x[i+1]
必须是 0 或 1,这相当于乘以 2 并可选地加 1。
因此,如果您知道S[i]
和x[i+1]
,则可以计算S[i+1]
。 这个描述听起来是不是很熟悉?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.