我有一个我必须扫描的文本正文,每行包含至少2个,有时包含4个部分的信息。问题是每行可以是15-20个不同行为中的1个。
在ruby中,当前代码看起来有点像这样:
text.split("\n").each do |line| #around 20 times.. .............. expressions['actions'].each do |pat, reg| #around 20 times .................
这显然是“问题”。 通过将所有regexen合并为一个,我确实设法让它更快(在C ++中以50%的利润率),但这仍然不是我需要的速度 - 我需要快速解析数千个这些文件!
现在我将它们与正则表达式相匹配 - 但这是无法忍受的缓慢。我开始使用ruby并跳转到C ++,希望我能获得速度提升而且它不会发生。
我随便读了关于PEG和基于语法的解析,但看起来有点难以实现。这是我应该走的方向还是有不同的路线?
基本上我正在解析扑克手牌历史,手牌历史的每一行通常都包含我需要收集的2-3位信息: 玩家是谁,行动需要多少钱或什么牌......等等。
需要解析的示例文本:
buriedtens posts $5 The button is in seat #4 *** HOLE CARDS *** Dealt to Mayhem 31337 [8s Ad] Sherwin7 folds OneMiKeee folds syhg99 calls $5 buriedtens raises to $10
收集此信息后,每个操作都会转换为xml节点。
现在我的ruby实现比我的C ++更快,但这是可能的。只是因为我没有用c代码写了超过4 - 5年
更新 我不想在这里发布所有代码,但到目前为止我的手/秒看起来如下:
588 hands/second -- boost::spirit in c++ 60 hands/second -- 1 very long and complicated regex in c++ (all the regexen put together) 33 hands/second -- normal regex style in ruby
我正在测试antlr以确定我们是否可以继续进行,但截至目前我对精神的结果非常满意。
相关问题:Efficiently querying one string against multiple regexes.
答案 0 :(得分:7)
我建议
答案 1 :(得分:4)
Boost.Spirit是一个很棒的库,它允许您进行详细的解析器分析,并且由于解析器是在代码中生成和编译的,因此应该比动态计算的解决方案快得多。语法主要使用表达式模板(许多重载运算符的一个奇特术语)完成,这意味着您实际上将它们写入代码中。
答案 2 :(得分:2)
如果你使用Perl,这是一种方法。
从perldoc perlfaq6
while (<>) {
chomp;
PARSER: {
m/ \G( \d+\b )/gcx && do { print "number: $1\n"; redo; };
m/ \G( \w+ )/gcx && do { print "word: $1\n"; redo; };
m/ \G( \s+ )/gcx && do { print "space: $1\n"; redo; };
m/ \G( [^\w\d]+ )/gcx && do { print "other: $1\n"; redo; };
}
}
对于每一行,PARSER
循环首先尝试匹配一系列数字后跟一个单词边界。此匹配必须从最后一场比赛停止的位置(或第一场比赛中的字符串的开头)开始。由于m/ \G( \d+\b )/gcx
使用c
标志,如果字符串与该正则表达式不匹配,则perl不会重置pos()
,并且下一个匹配将从相同位置开始尝试不同的模式。< / p>
答案 3 :(得分:1)
见Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby, ...)。根据数据量和正则表达式的复杂程度,编写自己的解析逻辑可能会更快。
答案 4 :(得分:1)
我随便读了关于PEG和基于语法的解析,但看起来有点难以实现。这是我应该走的方向还是有不同的路线?
就个人而言,我已经爱上了PEG。它可能需要一点时间来适应它们,但是我认为它们更易于维护,这是一个明显的胜利。当你在输入中找到新的边缘情况时,我发现解析代码是许多意外错误的根源。与循环和条件重正则表达式代码相比,具有非终结符的声明式语法更容易在发生这种情况时进行更新。命名很有用。
在Ruby中,Treetop是一个使用PEG的解析器生成器。我最近发现用一个简短的语法替换正则表达式重手写的解析器非常愉快。
答案 5 :(得分:0)
正则表达式匹配是否重叠?也就是说,当两个或多个正则表达式匹配同一行时,它们是否总是匹配行的不同部分(没有重叠)?
如果匹配从不重叠,请使用一个结合了现在15个正则表达式的正则表达式运行搜索:
regex1|regex2|regex3|...|regex15
如果您需要确定15个正则表达式中的哪个匹配,请使用捕获组。
搜索一次长期正则表达式的数据将比搜索15次更快。更快的速度取决于您正在使用的正则表达式引擎以及正则表达式的复杂性。
答案 6 :(得分:0)
在Perl中尝试一个简单的测试。阅读“学习”功能。我可能会尝试的是:
我没有尝试过,但它可能很有趣。
答案 7 :(得分:0)
另一个想法是,如果你有一个spiffy quad或oct核心服务器用于此目的。
构建一个分割工作的处理管道。第一阶段可以将文件切割成一个游戏或每个游戏,然后将每个文件写入八个第二阶段管道中的一个,这两个管道读取数据,处理它并以某种方式产生输出,可能是另一台机器上的数据库。
根据我的经验,这些基于管道的多工艺设计几乎与多线程设计一样快速且更容易调试。使用网络套接字而不是管道来设置一组机器也很容易。
答案 8 :(得分:0)
好的,这让事情更清晰(扑克手牌历史)。我猜你正在制作一个统计工具(攻击因素,去摊牌,自愿把$放到底池等)。我不确定为什么你需要过快的速度;即使您使用16个桌子进行多重操作,手也只能以适中的速度进行。
我不知道Ruby,但是在Perl中我会做一个小的switch语句,同时将重要的部分变成$ 1,$ 2等。根据我的经验,这并不比进行字符串比较慢然后用其他方法分割线。
HAND_LINE: for ($Line)
{ /^\*\*\* ([A-Z ]+)/ and do
{ # parse the string that is captured in $1
last HAND_LINE; };
/^Dealt to (.+) \[(.. ..)\]$/ and do
{ # $1 contains the name, $2 contains the cards as string
last HAND_LINE; };
/(.+) folds$/ and do
{ # you get the drift
last HAND_LINE; }; };
我认为你不能真正让它变得更快。对第一个位置出现最多的行(可能是fold语句)以及最后只出现稀疏的行(对象是"*** NEXT PHASE ***"
开始)的检查。
如果您发现实际文件读取是瓶颈,您可以查看可用于处理大文件的模块;对于Perl,我会想到Tie::File
。
确保只阅读每只手一次。每手牌后不要再读取所有数据,而是保持不变。已解析的手ID的哈希表。
答案 9 :(得分:0)
对于这样的问题,我只是闭上眼睛并使用Lexer + Parser生成器。您可以通过手动优化来击败它,但使用发电机要容易得多。此外,当输入突然改变时,它会更灵活。