如何克服 JavaScript 代码中缺少 Perl 的 \G 的问题?
Posted
技术标签:
【中文标题】如何克服 JavaScript 代码中缺少 Perl 的 \\G 的问题?【英文标题】:How to overcome the lack of Perl's \G in JavaScript code?如何克服 JavaScript 代码中缺少 Perl 的 \G 的问题? 【发布时间】:2018-01-08 10:03:42 【问题描述】:在 Perl 中,当想要对字符串进行连续解析时,可以这样做 我的 $string = " a 1 # ";
while ()
if ( $string =~ /\G\s+/gc )
print "whitespace\n";
elsif ( $string =~ /\G[0-9]+/gim )
print "integer\n";
elsif ( $string =~ /\G\w+/gim )
print "word\n";
else
print "done\n";
last;
来源:When is \G useful application in a regex?
它产生以下输出:
whitespace
word
whitespace
integer
whitespace
done
在 javascript(以及许多其他正则表达式风格)中,没有 \G
模式,也没有任何好的替代品。
所以我想出了一个非常简单的解决方案来满足我的目的。
<!-- language: lang-js -->
//*************************************************
// pattmatch - Makes the PAT pattern in ST from POS
// notice the "^" use to simulate "/G" directive
//*************************************************
function pattmatch(st,pat,pos)
var resu;
pat.lastIndex=0;
if (pos===0)
return pat.exec(st); // busca qualquer identificador
else
resu = pat.exec(st.slice(pos)); // busca qualquer identificador
if (resu)
pat.lastIndex = pat.lastIndex + pos;
return resu;
// if
所以,上面的例子在 JavaScript (node.js
) 中看起来像这样:
<!-- language: lang-js -->
var string = " a 1 # ";
var pos=0, ret;
var getLexema = new RegExp("^(\\s+)|([0-9]+)|(\\w+)","gim");
while (pos<string.length && ( ret = pm(string,getLexema,pos)) )
if (ret[1]) console.log("whitespace");
if (ret[2]) console.log("integer");
if (ret[3]) console.log("word");
pos = getLexema.lastIndex;
// While
console.log("done");
它产生与 Perl 代码 sn-p 相同的输出:
whitespace
word
whitespace
integer
whitespace
done
注意解析器在#
字符处停止。可以从pos
位置继续解析另一个代码sn-p。
❖
JavaScript 中有没有更好的方法来模拟 Perl 的 /G
正则表达式模式?
后版
出于好奇,我决定将我的个人解决方案与@georg 提案进行比较。这里我没有说明哪个代码最好。对我来说,这是一个品味问题。
我的系统在很大程度上依赖于用户交互,它会变慢吗?
@ikegami 写了关于@georg 解决方案的文章:
...他的解决方案是减少您输入的次数 文件被复制...
所以我决定在重复代码 1000 万次的循环中比较这两种解决方案:
<!-- language: lang-js -->
var i;
var n1,n2;
var string,pos,m,conta,re;
// Mine code
conta=0;
n1 = Date.now();
for (i=0;i<10000000;i++)
string = " a 1 # ";
pos=0, m;
re = new RegExp("^(\\s+)|([0-9]+)|(\\w+)","gim");
while (pos<string.length && ( m = pattMatch(string,re,pos)) )
if (m[1]) conta++;
if (m[2]) conta++;
if (m[3]) conta++;
pos = re.lastIndex;
// While
n2 = Date.now();
console.log('Mine: ' , ((n2-n1)/1000).toFixed(2), ' segundos' );
// Other code
conta=0;
n1 = Date.now();
for (i=0;i<10000000;i++)
string = " a 1 # ";
re = /^(?:(\s+)|([0-9]+)|(\w+))/i;
while (m = string.match(re))
if (m[1]) conta++;
if (m[2]) conta++;
if (m[3]) conta++;
string = string.slice(m[0].length)
n2 = Date.now();
console.log('Other: ' , ((n2-n1)/1000).toFixed(2) , ' segundos');
//*************************************************
// pattmatch - Makes the PAT pattern in ST from POS
// notice the "^" use to simulate "/G" directive
//*************************************************
function pattMatch(st,pat,pos)
var resu;
pat.lastIndex=0;
if (pos===0)
return pat.exec(st);
else
resu = pat.exec(st.slice(pos));
if (resu)
pat.lastIndex = pat.lastIndex + pos;
return resu;
// pattMatch
结果:
我的:11.90 秒 其他:10.77 秒
我的代码运行时间长了大约 10%。每次迭代花费大约 110 纳秒。
老实说,根据我个人的喜好,在一个用户交互繁重的系统中,我可以接受这种效率损失。
如果我的项目涉及使用多维数组或巨大的神经网络进行繁重的数学处理,我可能会重新考虑。
【问题讨论】:
在您的 Perl 代码中,您可能需要/c
标志而不是/i
或/m
。
谢谢,melpomene,我现在就修好
/gim
应该是 /gc
。如果没有/c
,匹配位置将在匹配失败时重置。 (/i
和 /m
对这些模式毫无用处。)
Cleaned up version of your Perl code
【参考方案1】:
\G
的功能以/y
flag 的形式存在。
var regex = /^foo/y;
regex.lastIndex = 2;
regex.test('..foo'); // false - index 2 is not the beginning of the string
var regex2 = /^foo/my;
regex2.lastIndex = 2;
regex2.test('..foo'); // false - index 2 is not the beginning of the string or line
regex2.lastIndex = 2;
regex2.test('.\nfoo'); // true - index 2 is the beginning of a line
但它很新。您还不能在公共网站上使用它。检查链接文档中的浏览器兼容性图表。
【讨论】:
我不知道,@ikegami,谢谢你的知识分享!不幸的是,我无法在我的项目中使用此功能,这需要与旧版本有一定的兼容性。那么问题来了:在没有这个新特性的情况下,有没有更优化的方式(除了我的)来模拟/G? 现在我认为在循环中保存函数调用并不那么重要。对于我的应用程序的处理类型,我宁愿为它付出代价,也不愿在处理过程中连续切割字符串。这是因为,在我的实际过程中,我想在 string 中保存一些位置以供进一步处理,并且相对位置会降低清晰度,即使我可以挽救绝对位置。所以你引用的/Y子句,/G子句有它的价值。 我已经编辑了我的 javascript 代码,以便拥有与 @georg javascript 代码相同的行数。不同之处在于一个额外的函数调用和我保持索引和模式匹配目标完整性的选项。 Re "现在我认为在循环中保存函数调用并不那么重要。",如果你认为必须这样做,你真的错过了他们解决方案的重点处理函数调用?!?他的解决方案增加的是减少输入文件的复制次数。 抱歉我的无知,但您指的是哪个输入文件?源代码?字符串,正则表达式目标,no 是否已经在内存中?执行模式识别的例程 (exec) 在两种情况下被调用相同的次数,在一种情况下直接调用,另一种在函数 pattmatch 中调用。我编辑的解决方案与另一个解决方案的行数相同。循环执行相同的次数。一定是有什么明显的东西在逃避我,但我不明白你在说什么。【参考方案2】:看起来你有点过于复杂了。 exec
和 g
标志提供开箱即用的锚定:
var
string = " a 1 # ",
re = /(\s+)|([0-9]+)|(\w+)|([\s\S])/gi,
m;
while (m = re.exec(string))
if (m[1]) console.log('space');
if (m[2]) console.log('int');
if (m[3]) console.log('word');
if (m[4]) console.log('unknown');
如果您的正则表达式没有覆盖,并且您想在第一个不匹配时停止,最简单的方法是从 ^
匹配并在匹配后剥离字符串:
var
string = " a 1 # ",
re = /^(?:(\s+)|([0-9]+)|(\w+))/i,
m;
while (m = string.match(re))
if (m[1]) console.log('space');
if (m[2]) console.log('int');
if (m[3]) console.log('word');
string = string.slice(m[0].length)
console.log('done, rest=[%s]', string)
这个简单的方法不能完全取代\G
(或你的“匹配自”方法),因为它丢失了匹配的左侧上下文。
【讨论】:
我可能会错过一些东西,但我认为它不能满足我的要求。它跳过 "#" 字符并扫描之后的空格字符。我不想要它,因为我希望能够在另一个 sn-p 代码中扫描 #。此外,该解决方案忽略了不适合的嵌入部分,这违反了编译器或转译器中连续解析的概念。例如,如果字符串是 "& a & 1 # ",则结果将是 space word space space int space space 这将违反我在上面创建的“语法” .否则,/G 在 Perl 中将毫无用处。 @PauloBuchsbaum:当然,现实世界的正则表达式应该涵盖所有可能的情况,最后一个(备用)组([\s\S])
。查看更新
这很难解释。上面的正则表达式仅用于说明。我的真实应用程序比这复杂得多,但我将尝试在下面阐明:我的真正目标是将指针停止在不适合这种模式匹配的字符串的第一部分,所以在下面的代码 sn-p 中,我将用另一个正则表达式处理它。在这种情况下(字符串“a 1 #”),我需要在“#”字符处停止,而不是跳过不需要的部分并继续使用相同的正则表达式。毕竟,Perl 一定是出于某种原因在正则表达式规范中添加了 "/G"。
@PauloBuchsbaum:我知道你来自哪里,更新了答案。
是的,很好,谢谢@georg。它完美无缺,但是我仍然更喜欢我的方法,因为正则表达式有点简单,并且不需要在每次迭代中更改字符串。更改在例程 pattmatch 中被封装和本地化。我将编辑我的代码以使其更简单。以上是关于如何克服 JavaScript 代码中缺少 Perl 的 \G 的问题?的主要内容,如果未能解决你的问题,请参考以下文章