可能会遇到以下两行文本:
约翰的新车
约翰的车
修饰符“new”是可选的。我认为这会起作用:
([a-zA-Z'\s]+)\s?(new)?\s?(car)
根据 Rubular 的说法,对于第一种情况,这给出了["John's new", "", "car"]
. 我正在寻找的是:
约翰的新车
["John's", "new", "car"]
在这种情况下:
John's car
["John's", {}, "car"]
([a-zA-Z'\s]+?)\s?(new)?\s?(car)
^ added
您需要使第一个子组不贪心,它正在吃掉第二个子组的比赛。
>> /([a-zA-Z'\s]+?)\s?(new)?\s?(car)/.match "John's new car"
=> #<MatchData "John's new car" 1:"John's" 2:"new" 3:"car">
>> /([a-zA-Z'\s]+?)\s?(new)?\s?(car)/.match "John's car"
=> #<MatchData "John's car" 1:"John's" 2:nil 3:"car">
是的,可能会遇到其他词。但我只会使用 ([a-zA-Z'\s]+)\s?(new|old|fast|slow)?\s?(car)
这不是一个好的计划,因为您可能有一个非常大的可选单词列表以及更新源代码的持续任务。
更好的解决方案是将可选单词放入 YAML 文件中,在运行时加载它,从中创建一个正则表达式,然后将其插入到正确位置的模式中。
为什么选择 YAML 格式?它对您来说很容易阅读,并且很容易被多种语言加载/解析。如果您愿意,可以使用文本平面文件。
为什么要创建一个正则表达式模式而不是循环遍历列表?因为如果操作正确,正则表达式会更快更准确。
以下是我的做法:
将其保存到名为“test.yaml”的 YAML 文件中:
---
- red
- blue
- green
- yellow
- fast
- slow
- old
- new
将此保存到“test.rb”:
我是这样钓鱼的:
#!/usr/bin/env ruby
require 'pp'
require 'yaml'
adjectives = YAML.load_file('./test.yaml')
adjective_regex = /(?:\b#{ Regexp.union(adjectives).source }\b)/i
search_regex = /([a-z']+) \s+ (#{ adjective_regex }?) \s? (car)/ix
[
"John's car",
*adjectives.map{ |a| "John's #{ a } car" }
].each do |s|
s[search_regex]
pp [$1, $2.empty? ? {} : $2, $3]
end
运行输出:
["John's", {}, "car"]
["John's", "red", "car"]
["John's", "blue", "car"]
["John's", "green", "car"]
["John's", "yellow", "car"]
["John's", "fast", "car"]
["John's", "slow", "car"]
["John's", "old", "car"]
["John's", "new", "car"]
此时,维护应用程序不需要修改代码,而是修改数据。
现在,Perl 有一个名为Regexp::Assemble的模块,它对这种用途非常有用。它允许我们获取单词列表并生成一个非常有效的模式来处理搜索:
而不是一个看起来像的正则表达式"red|blue|green|yellow|fast|slow|old|new"
,它看起来像:
(?-xism:(?:(?:(?:yel|s)lo|ne)w|(?:ol|re)d|green|blue|fast))
这是生成该模式的代码:
use Regexp::Assemble;
my $ra = Regexp::Assemble->new;
my @adjectives = qw[red blue green yellow fast slow old new];
foreach my $a (@adjectives) {
$ra->add($a);
}
print $ra->re, "\n";
此示例不会创建更短的模式,但添加的单词越多,模式就越优化。它可以产生的东西非常惊人。重要的是,您可以轻松地使用代码生成列表,并使用它为 Ruby 解析器构建正则表达式。