给定一个十进制数N
作为一个数字串,我如何检查它是否能被M
整除只使用正则表达式,而不转换为int?
M=2、4、5、10是明显的。对于M=3,这里有一些有趣的见解:Regex filter numbers divisible by 3
有没有人能给出M=7、9、11、13等的解?一个普通的?
测试代码(Python中,但可以自由使用任何语言):
M = your number, e.g. 2
R = your regexp, e.g., '^[0-9]*[02468]$'
import re
for i in range(1, 2000):
m = re.match(R, str(i))
if i % M:
assert not m, '%d should not match' % i
else:
assert m, '%d must match' % i
对于那些好奇的人,这里有一个M=3
的例子(假设引擎支持递归):
^
(
| [0369]+ (?1)
| [147] (?1) [258] (?1)
| [258] (?1) [147] (?1)
| ( [258] (?1) ) {3}
| ( [147] (?1) ) {3}
)
$
Upd:更多讨论和示例请参见thread。那里贴出的表达结果是错误的(在70*N上失败),但“如何到达那里”的部分是非常有教育意义的。
7条答案
按热度按时间xwbd5t1u1#
也许令人惊讶的结果是这样的正则表达式总是存在的。不那么令人惊讶的是,它通常没有用处。
存在性结果来自deterministic finite automata(DFA)与正则表达式之间的对应关系。让我们做一个这样的DFA。用 N 表示模数(它不需要是素数),用 B表示数字基数, 对于普通十进制数是10。具有 N 个状态的DFA标记为0到 N−1。初始状态为0。DFA的符号是数字0到 B−1。状态表示输入字符串的左前缀的余数,当除以 N时被解释为整数。 边缘表示当您向右侧添加数字时状态的变化。从算术上讲,这是州Map
接受状态是0,因为零余数表示可整除性。我们有一个DFA。DFA识别的语言与正则表达式识别的语言相同,因此存在一种。虽然这很有趣,但没有帮助,因为它没有告诉你如何确定表达式。
如果您想要一个泛型算法,很容易在运行时构建这样的DFA,并通过直接计算填充其状态表。初始化只是一对嵌套循环,运行时间为O(M × N)。机器的识别是每个输入字符的恒定时间。这是非常快的,但不使用regexp库,如果这是你真正需要的。
为了得到一个真正的正则表达式,我们需要看看Fermat's Little Theorem。从定理中我们知道
例如,当 N = 7和 B = 10时,这意味着每个6位数的块等价于集合{0,...,6}中的某个单个数字,以便于整除。指数可以小于 N−1;一般来说,它是 N的Euler’s totient function的因子。 称块的大小为 D。对于 D 个数字的块,有 N 个正则表达式,每个正则表达式表示模 N的余数的特定等价类。 这些表达式的长度最多为O(B**D),这是很大的。对于 N = 7,这是一组一百万个字符长的正则表达式;我想这会破坏大多数regexp库。
这与示例代码中的表达式如何工作有关;表达式
(?1)
匹配≡ 0(mod 3)的字符串。这对 N = 3起作用,因为101 ≡ 1(mod 3),这意味着 A0B ≡ AB(mod 3)。当指数大于1时,这更加复杂,但原理是相同的。(注意,严格地说,示例代码使用的识别器不仅仅是正则表达式。)表达式[0369]
、[147]
和[258]
是模3表达式中数字0、1和2的正则表达式。一般来说,您可以以类似的方式使用上面的regexp-digits。我没有提供代码,因为它需要比这个答案更长的时间来编写,而且我真的怀疑它会在任何已知的实现中执行。
8ljdwjyq2#
如果你的数字是基于一元的,你可以使用这个正则表达式:
s/1{divisor}//g
然后测试数字是否为空。下面是一种Perl方法
输出:
vyswwuz23#
这是一个老问题,但现有的答案都不包括代码。我在2010年写了代码来计算这些正则表达式which is online here,并有一个到the commented source code的链接,所以我想在这里添加一个链接可能会有用。
该技术基本上与eh9’s answer中描述的技术相同,只是我使用状态消除直接从DFA计算正则表达式,然后对结果进行一些简化。它们是普通的正则表达式,没有递归。(使用递归会使结果更短,但我认为有趣的是,没有递归也可能。
结果并不像eh9估计的那么冗长。例如,下面是一个匹配以10为基数的7的倍数的正则表达式:
它“只有”12,733个字符,在我尝试过的实现中运行良好。
PS.要了解使用递归的有趣程度有多低,这里有一个Perl程序,它使用递归正则表达式测试7整除性:
这只是DFA作为递归正则表达式的直接表达式。
suzh9iv84#
如果允许修改字符串并重复,则可以一次执行一步长除法。7的sed语法:重新申请,直到你得到剩余的。当您有一个0、1、2、3、4、5或6时停止。
但这是很淡的酱料。更简单的方法是完全取消“正则表达式”,一次只读入一个字符并切换到适当的状态。如果这不是一个选择,那么我怀疑你是运气7和13,虽然11可能仍然是可能的。
tag5nh1u5#
这里是一个通用的直接递归蛮力长除法。它没有优化,绝对不是优雅的笑。它是在JavaScript(下面是一个测试的html页面的代码):
假设M = 7(例如):
ekqde3dh6#
尽管这个问题很有趣,但我不相信除了你列出的那些“显而易见”的问题之外,还有什么可能。
大多数divisibility rules需要数学运算。
你可以使用lookahead to test the string for more than one requirement,这样你就可以把“明显的”对组合在一起(2 x 3,3 x 5等):
使用
\b\w{6}\b
匹配6个字母的单词很容易。匹配一个包含“cat”的单词同样简单:\b\w*cat\w*\b
。结合两者,我们得到:
(?=\b\w{6}\b)\b\w*cat\w*\b
使用RegexBuddy分析此正则表达式。简单!我们这么做。在字符串中尝试正则表达式的每个字符位置,引擎将首先在正向前瞻内尝试正则表达式。只有当字符串中的当前字符位置位于字符串中一个6个字母的单词的开头时,这个子正则表达式才匹配,因此lookahead也匹配。如果没有,则先行将失败,引擎将继续从字符串中的下一个字符位置开始尝试正则表达式。dldeef677#
当你检查一个二进制数是否能被7整除时,你可以尝试以下方法:
(0|1(0([01](1|0{2}))*(10|(0|1{2})01)|1{2})(01*0(0|101|1(1|0{2})((0|1{2})(1|0{2}))*(10|(0|1{2})01)))*1)+
正则表达式