目前我有一个正则表达式,看起来像这样:
^(cat|dog|bird){1}(cat|dog|bird)?(cat|dog|bird)?$
该正则表达式匹配至少1个、最多3个单词列表中的实例,并将每个组的匹配单词通过相应的变量可用。
是否有一种方法可以修改它,以便我可以返回字符串中每个单词的结果,而不需要预先指定组数?
^(cat|dog|bird)+$
该函数可以运行,但是只返回最后一个匹配项,因为只有一个分组。
好的,所以我找到了解决办法。
看起来不可能创建未知数量的组,因此我寻找另一种实现所需结果的方法:能够确定一个字符串是否由给定列表中的单词组成;并匹配每个位置上可能的最长单词。
我一直在阅读Jeffrey E. F. Friedl的《精通正则表达式》,它为我提供了一些启示。原来基于NFA的正则表达式引擎(如Ruby中使用的引擎)是顺序的,同时也是懒惰/贪婪的。这意味着您可以使用给出选择的顺序来指定如何匹配模式。这就解释了为什么扫描返回可变结果,它正在寻找第一个符合条件的列表中的单词,然后转移到下一个匹配项。按设计,它不是在寻找最长匹配,而是第一个匹配项。因此,为了纠正这种情况,我只需要将用于生成正则表达式的单词数组从字母顺序重新排序为长度顺序(从最长到最短)。
array = %w[ as ascarid car id ]
list = array.sort_by {|word| -word.length }
regexp = Regexp.union(list)
现在通过扫描找到的第一个匹配项将是最长的可用单词。使用扫描也很容易判断字符串是否仅包含列表中的单词:
if "ascarid".scan(regexp).join.length == word.length
return true
else
return false
end
感谢所有回答此问题的人,我希望这些内容能够帮助其他人。
/a|aa/
从左到右匹配的方法,获得额外确认会很好。如果需要一步完成,您可以使用 array.sort_by {|word| -word.length }
。 - mu is too short/^(cat|dog|bird)+$/
(或更好的 /\A(cat|dog|bird)+\z/
)来确保匹配。string.scan(/cat|dog|bird/)
来获取这些部分。你也可以使用 split
和 Set 一次完成。假设你的单词在数组 a
中,你的字符串在 s
中,则:
words = Set.new(a)
re = /(#{a.map{|w| Regexp.quote(w)}.join('|')})/
parts = s.split(re).reject(&:empty?)
if(parts.any? {|w| !words.include?(w) })
# 's' didn't match what you expected so throw a
# hissy fit, format the hard drive, set fire to
# the backups, or whatever is appropriate.
else
# Everything you were looking for is in 'parts'
# so you can check the length (if you care about
# how many matches there were) or something useful
# and productive.
end
split
时,相应的匹配也将在数组中返回。split
将会给我们一些类似于 ["", "cat", "", "dog"]
的东西,而空字符串只会出现在我们正在寻找的分隔符之间,因此我们可以 reject
它们并假装它们不存在。这可能是对 split
的一个意外用法,因为我们更关心的是分隔符而不是被分隔的内容(除了确保没有被分隔的内容),但它完成了工作。
(ascarid|car|as|id)
就会从左到右尝试匹配。我在 Ruby Oniguruma(Ruby 1.9 正则表达式引擎)文档中找不到任何关于 |
是有序还是无序的说明;Perl 的交替 显然被指定为有序,而 Ruby 的行为肯定表现得像是有序的:>> 'pancakes' =~ /(pan|pancakes)/; puts $1
pan
re = /(#{a.sort_by{|w| -w.length}.map{|w| Regexp.quote(w)}.join('|')})/
if(s !~ /\A(#{a.map{|w| Regexp.quote(w)}.join('|')})+\z/)
# Bail out and complain that 's' doesn't look right
end
by_length = a.group_by(&:length)
# This loses the order of the substrings within 's'...
matches = [ ]
by_length.keys.sort_by { |k| -k }.each do |group|
re = /(#{a.map{|w| Regexp.quote(w)}.join('|')})/
s.gsub!(re) { |w| matches.push(w); '' }
end
# 's' should now be empty and the matched substrings will be
# in 'matches'
r = "(cat|dog|bird)"
str.match(/#{r}#{r}?#{r}?/)
您可以使用 .Net 正则表达式完成此操作。如果我在 PowerShell 中编写以下内容
$pat = [regex] "^(cat|dog|bird)+$"
$m = $pat.match('birddogcatbird')
$m.groups[1].captures | %{$_.value}
然后我得到
bird
dog
cat
bird
string.scan(/cat|dog|bird/)
的原因是什么? - riffraff'dogpancakesbird'.scan(/cat|dog|bird/)
@riffraff:'dogpancakesbird'.scan(/cat|dog|bird/)
- mu is too short