我正在寻找一种方法来检查字符串是否定期使用JavaScript。使用字符串函数查找定期字符串
匹配的样本字符串可以是11223331122333
。而不应该匹配。
从巨蟒的到来,我用正则表达式
/(.+?)\1+$/
但它是相当缓慢。有没有任何字符串方法可以做到这一点?
我正在寻找一种方法来检查字符串是否定期使用JavaScript。使用字符串函数查找定期字符串
匹配的样本字符串可以是11223331122333
。而不应该匹配。
从巨蟒的到来,我用正则表达式
/(.+?)\1+$/
但它是相当缓慢。有没有任何字符串方法可以做到这一点?
下面的代码的想法是考虑所有长度的子串,原始字符串可以分成均匀的,并检查它们是否重复跨越原始字符串。一个简单的方法是检查长度从1到长度的平方根的所有除数。如果分部产生一个整数,则它们是除数,这也是一个补充除数。例如,对于长度为100的字符串,除数为1,2,4,5,10,并且互补除数为100(因为子字符串将仅出现一次,所以不用作子字符串长度),50,25,20(和10 ,我们已经找到了)。
function substr_repeats(str, sublen, subcount)
{
for (var c = 0; c < sublen; c++) {
var chr = str.charAt(c);
for (var s = 1; s < subcount; s++) {
if (chr != str.charAt(sublen * s + c)) {
return false;
}
}
}
return true;
}
function is_periodic(str)
{
var len = str.length;
if (len < 2) {
return false;
}
if (substr_repeats(str, 1, len)) {
return true;
}
var sqrt_len = Math.sqrt(len);
for (var n = 2; n <= sqrt_len; n++) { // n: candidate divisor
var m = len/n; // m: candidate complementary divisor
if (Math.floor(m) == m) {
if (substr_repeats(str, m, n) || n != m && substr_repeats(str, n, m)) {
return true;
}
}
}
return false;
}
不幸的是,对于在适当位置比较另一个字符串的子串(例如,在那将是strncmp(str1, str2 + offset, length)
C语言)没有字符串方法。
假设您的字符串长度为120,并且由长度为6的子字符串重复20次组成。你可以把它看作是由一个重复次数为10的次长(重复长度)12,重复5次的重复次数24,重复次数为4的重复次数30或重复次数为60的重复长度60组成(重复次数由20 (2 * 2 * 5)以不同组合应用于6)。现在,如果你检查你的字符串是否包含60的重复长度重复2次,并且检查失败,那么你也可以确定它不包含作为除数(即主要因素的组合)为60的任何长度,其中包括6.换句话说,上述代码所做的很多检查都是多余的。例如,在长度为120的情况下,上述代码检查(幸运的是,大部分时间很快失败)下列超长:1,2,3,4,5,6,8,10,12,15,20,24, (按此顺序:1,60,2,40,3,30,4,24,5,20,6,15,8,12,10)中的一个或多个。其中,只有以下是必要的:24,40,60。它们是2 * 2 * 2 * 3,2 * 2 * 2 * 5,2 * 2 * 3 * 5,即质数的组合120( 2 * 2 * 2 * 3 * 5),每个(非重复)素数中的一个被取出,或者,如果您愿意,可以是120/5,120/3,120/2。因此,暂时忘记有效的素因子分解不是一项简单的任务,我们可以将重复子串的检查限制为子长度为p的子串,其中p是长度的主要因子。以下是最简单平凡的实现:
function substr_repeats(str, sublen, subcount) { see above }
function distinct_primes(n)
{
var primes = n % 2 ? [] : [2];
while (n % 2 == 0) {
n /= 2;
}
for (var p = 3; p * p <= n; p += 2) {
if (n % p == 0) {
primes.push(p);
n /= p;
while (n % p == 0) {
n /= p;
}
}
}
if (n > 1) {
primes.push(n);
}
return primes;
}
function is_periodic(str)
{
var len = str.length;
var primes = distinct_primes(len);
for (var i = primes.length - 1; i >= 0; i--) {
var sublen = len/primes[i];
if (substr_repeats(str, sublen, len/sublen)) {
return true;
}
}
return false;
}
试图从我的Linux PC上这个代码,我有一个惊喜:在Firefox它比第一个版本快得多,但铬是慢,成为仅适用于长度为数千的字符串。最后我发现问题与distinct_primes()
创建并传递到is_periodic()
的数组有关。解决方案是通过合并这两个函数来摆脱数组。该代码是下面和测试结果上http://jsperf.com/periodic-strings-1/5
function substr_repeats(str, sublen, subcount) { see at top }
function is_periodic(str)
{
var len = str.length;
var n = len;
if (n % 2 == 0) {
n /= 2;
if (substr_repeats(str, n, 2)) {
return true;
}
while (n % 2 == 0) {
n /= 2;
}
}
for (var p = 3; p * p <= n; p += 2) {
if (n % p == 0) {
if (substr_repeats(str, len/p, p)) {
return true;
}
n /= p;
while (n % p == 0) {
n /= p;
}
}
}
if (n > 1) {
if (substr_repeats(str, len/n, n)) {
return true;
}
}
return false;
}
请记住,通过jsperf.org收集的定时是绝对的,并且与不同的机器,不同的实验者将有助于信道的不同组合。如果你想可靠地比较两个JavaScript引擎,你需要编辑一个新的私有版本的实验。
也是,谢谢你的解释 - 确实使它更容易遵循(现在我也+1)@BhargavRao - 只是想知道你是如何测试这些速度的吗?你可以在jsperf.com或类似的地方公开的东西?将是int希望看到你的原始方法与其他方法相比的一些结果。 –
@JamesThorpe Nope。我有几个输入文件。我只是在上面运行代码。如果可能的话(cc Walter),你可以添加一个包含不同时间的CW吗? (在[python]中,我们确实喜欢这几个问题)。我不知道如何衡量我会做的时间。 –
[这里是性能测试](http://jsperf.com/periodicstrings/2)(cc @BhargavRao) - 看起来像一个锚定的懒惰正则表达式,上面的函数远远执行贪婪的。最初的unanchored懒惰正则表达式也很快,但我会[对于结果的可疑](https://regex101.com/r/mX3sB6/2)。 –
一种选择是继续使用正则表达式,而是使之贪婪通过降低?
:
/^(.+)\1+$/
取决于精确的输入字符串,它可能会降低回溯所需的量,加快配套。
@BhargavRao不用担心 - 这可能不是一个好的答案,因为我认为它将取决于匹配的输入字符串。在一般情况下,可能仍然是一种更好的方式。 –
我可能是错的,但我认为当没有匹配时,只有尝试顺序在贪婪和懒惰版本之间改变。当有匹配的时候,我担心它的平均发现时间较迟。这里真正的加速来自^,它避免了在字符串开始处没有锚定的所有测试。 –
@WalterTross是的,我不确定这里的贪婪匹配是否更好。现在只需要进行一次jsperf测试 - 我第一次完成测试可能不太完美... –
如果字符串是周期性:
所以我们可以一个超级贪婪的算法,取最后一个元素并检查出现直到长度的一半。当我们找到一个时,我们检查子字符串的长度是否与主字符串长度相除,然后才检测字符串。
function periodic(str){
for(var i=0; i<=str.length/2; i++){
if(str[i] === str[str.length-1] && str.length%(i+1) === 0){
if (str.substr(0,i+1).repeat(str.length/(i+1)) === str){
return true;
}
}
}
return false;
}
嗨谢谢你的回答。请将您的代码添加到这里的perf比较中。 http://jsperf.com/periodic-strings-1 –
完成了,我认为结果看起来很有希望! –
糟糕,你需要添加到最新版本,这里http://jsperf.com/periodic-strings-1/8 ...这将比较其他答案 –
直接的办法是划分字符串转换成相等大小的块,并测试 每个夹头是否是相同的第一个块。这里有一个算法 ,通过将块大小从1增加到length/2,跳过块大小为 ,这些块不会干净地划分长度。
function StringUnderTest (str) {
this.str = str;
this.halfLength = str.length/2;
this.period = 0;
this.divideIntoLargerChunksUntilPeriodicityDecided = function() {
this.period += 1;
if (this.period > this.halfLength)
return false;
if (this.str.length % this.period === 0)
if (this.currentPeriodOk())
return true;
return this.divideIntoLargerChunksUntilPeriodicityDecided();
};
this.currentPeriodOk = function() {
var patternIx;
var chunkIx;
for (chunkIx=this.period; chunkIx<this.str.length; chunkIx+=this.period)
for (patternIx=0; patternIx<this.period; ++patternIx)
if (this.str.charAt(patternIx) != this.str.charAt(chunkIx+patternIx))
return false;
return true;
};
}
function isPeriodic (str) {
var s = new StringUnderTest(str);
return s.divideIntoLargerChunksUntilPeriodicityDecided();
}
我没有测试过的速度,虽然...
嗨谢谢你的回答。请将您的代码添加到这里的perf比较中。 http://jsperf.com/periodic-strings-1 –
上面的代码似乎是无可救药的缓慢(在jsperf上): -/ –
没问题。无论如何,这是一个很好的答案。 TY。 :) –
还有值得一提的其纯粹的美一个答案。它不是我的,我只是从Python版本,这是在这里将它改编:How can I tell if a string repeats itself in Python?
function is_periodic(s)
{
return (s + s.substring(0, s.length >> 1)).indexOf(s, 1) > 0;
}
不幸的是,速度不看齐的美丽(以及美遭受了位在适应从Python,因为indexOf()
有一个开始参数,但不是停止参数)。与正则表达式解决方案的比较以及我的其他答案的功能是here。即使以[4,400]中的一个随机长度为基础的字符串,我的其他答案的功能表现也会更好。不过,该解决方案比正则表达式解决方案更快。
该解决方案可能被称为“相移解决方案”。该字符串被视为一个波形,它在移动其相位时与自身相同。
该解决方案在我的其他答案的那些的优点是,它可以很容易地适应返回最短重复子,像这样:
function repeating_substr(s)
{
period = (s + s.substring(0, s.length >> 1)).indexOf(s, 1);
return period > 0 ? s.substr(0, period) : null;
}
一个小小的忏悔,我已经使用你的其他代码,并推动变化。所以不幸的是我不能测试这个代码(也不是其他2 [新](http://stackoverflow.com/a/34624246/4099593)[答案](http://stackoverflow.com/a/34642101/4099593))和我为此感到非常难过。然而,看到性能测量结果,我感到有点高兴,你的其他答案表现更好(*葡萄*确实*酸味*)。希望这会对其他人有所帮助。 –
嗨,为你参考http://jsperf.com/periodic-strings-1/10(我第一次使用jsperf这个问题) –
@BhargavRao你的测试设置是错误的,我修正了它:http: //jsperf.com/periodic-strings-1/11 –
会'112233311223331122333'也匹配吗?而我猜'112233311223331'不会? –
@JamesThorpe是的。正确。第一个匹配,但第二个不匹配 –
您需要'^'在正则表达式的开头,否则它将匹配:'“11010”'。 – andlrc