对于一门真正的语言,词法分析器是必经之路——就像 Guss 所说的那样。但是,如果完整的语言只和你的例子一样复杂,你可以使用这个快速破解:
irb> text = %{Children^10 Health "sanitation management"^5}
irb> text.scan(/(?:(\w+)|"((?:\\.|[^\\"])*)")(?:\^(\d+))?/).map do |word,phrase,boost|
{ :keywords => (word || phrase).downcase, :boost => (boost.nil? ? nil : boost.to_i) }
end
#=> [{:boost=>10, :keywords=>"children"}, {:boost=>nil, :keywords=>"health"}, {:boost=>5, :keywords=>"sanitation management"}]
如果您正在尝试解析常规语言,那么这种方法就足够了 - 尽管使语言变得非常规并不需要更多的复杂性。
正则表达式的快速分解:
\w+
匹配任何单项关键字
(?:\\.|[^\\"]])*
使用非捕获括号 ( (?:...)
) 来匹配转义双引号字符串的内容 - 转义符号 ( \n
, \"
,\\
等) 或任何不是转义符号或结束引号的单个字符。
"((?:\\.|[^\\"]])*)"
仅捕获引用的关键字词组的内容。
(?:(\w+)|"((?:\\.|[^\\"])*)")
匹配任何关键字 - 单个术语或短语,将单个术语捕获到$1
并将短语内容捕获到$2
\d+
匹配一个数字。
\^(\d+)
捕获插入符号 ( ^
) 后面的数字。由于这是第三组捕获括号,它将被捕获到$3
.
(?:\^(\d+))?
如果存在插入符号后面的数字,则捕获该数字,否则匹配空字符串。
String#scan(regex)
尽可能多地将正则表达式与字符串匹配,输出“匹配”数组。如果正则表达式包含捕获括号,则“匹配”是捕获的项目数组 - 因此$1
变为match[0]
、$2
变为match[1]
等。任何未与部分字符串匹配的捕获括号映射到nil
结果“匹配”中的条目。
然后#map
获取这些匹配项,使用一些块魔法将每个捕获的术语分解为不同的变量(我们可以这样做do |match| ; word,phrase,boost = *match
),然后创建您想要的哈希值。恰好是word
orphrase
之一nil
,因为两者都不能与输入匹配,所以(word || phrase)
将返回非nil
一,#downcase
并将其转换为全小写。 boost.to_i
将字符串转换为整数,同时(boost.nil? ? nil : boost.to_i)
确保nil
boosts 保持不变nil
。