我正在将一个可能很大的字符串(比方说20MB,虽然这完全是任意的)拆分成由正则表达式列表定义的标记。
我当前的算法采用以下方法:
^
#slice!
输入字符串#slice!
什么,我们得到一个匹配并且输入字符串已经准备好找到下一个标记(因为#slice!
修改了字符串)不幸的是,这很慢,这是由于长字符串上重复#slice!
...似乎修改ruby中的大字符串并不快。
所以我想知道是否有办法将我的正则表达式与新的子字符串(即字符串的其余部分)相匹配而不修改它?
(测试的,可运行的)伪代码中的当前算法:
rules = {
:foo => /^foo/,
:bar => /^bar/,
:int => /^[0-9]+/
}
input = "1foofoo23456bar1foo"
# or if you want your computer to cry
# input = "1foofoo23456bar1foo" * 1_000_000
tokens = []
until input.length == 0
matched = rules.detect do |(name, re)|
if match = input.slice!(re)
tokens << { :rule => name, :value => match }
end
end
raise "Uncomsumed input: #{input}" unless matched
end
pp tokens
# =>
[{:rule=>:int, :value=>"1"},
{:rule=>:foo, :value=>"foo"},
{:rule=>:foo, :value=>"foo"},
{:rule=>:int, :value=>"23456"},
{:rule=>:bar, :value=>"bar"},
{:rule=>:int, :value=>"1"},
{:rule=>:foo, :value=>"foo"}]
请注意,虽然非常简单地将正则表达式与字符串匹配相同的次数并不是很快,但是在你等待的时候你没有时间煮比萨饼(几秒钟,vs很多分钟。)
答案 0 :(得分:3)
String#match()
方法有一个双参数版本,它将匹配从字符串中特定字符位置开始的正则表达式。您只需要将one-past-the-last-matching-character from the previous match作为新匹配的起始位置。
在未经测试的非运行伪代码中:
input = "foo"
input_pos = 0
input_end = input.length
until input_pos == input_end do
matched = rules.detect do |(name, re)|
if match = input.match(re, input_pos)
tokens << { :rule => name, :value => match }
input_pos = match.post_match
end
end
end
答案 1 :(得分:1)
也许我过于简单了,但String#scan最有可能胜过其他任何事情:
tokens = input.scan(/foo|bar|\d+/).map{|m| {:value => m, :rule => rules.find{|name,re| m =~ re}[0]}}
或更一般地说:
rules = {
:foo => /foo/,
:bar => /bar/,
:int => /[0-9]+/
}
tokens = input.scan(Regexp.union(rules.values)).map{|m| {:value => m, :rule => rules.find{|name,re| m =~ re}[0]}}