正则表达式匹配具有重复模式的字符串

时间:2018-03-08 19:25:41

标签: ruby regex

我正在尝试找到一个匹配带有三个或更多重复段的URL的正则表达式(可能包含任意数量的目录),例如:

  • s1 = 'http://www.foo.com/bar/bar/bar/'
  • s2 = 'http://www.foo.com/baz/biz/baz/biz/baz/biz/etc'
  • s3 = '/foo/bar/foo/bar/foo/bar/'

并且不匹配以下网址:

  • s4 = '/foo/bar/foo/bar/foo/barbaz'

首先我尝试了:

re1 = /((.+\/)+)\1\1/

有效:

re1 === s1 #=> true
re1 === s2 #=> true

但随着段数的增加,正则表达式匹配呈指数级增长:

require 'benchmark'
Benchmark.bm do |b|
  (10..15).each do |num|
    str = '/foo/bar' * num
    puts str
    b.report("#{num} repeats:") { /((.+\/)+)\1\1/ === str }
  end
end

       user     system      total        real
/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar
    10 repeats:  0.060000   0.000000   0.060000 (  0.054839)
    /foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar
    11 repeats:  0.210000   0.000000   0.210000 (  0.213492)
    /foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar
    12 repeats:  0.870000   0.000000   0.870000 (  0.871879)
    /foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar
    13 repeats:  3.370000   0.010000   3.380000 (  3.399224)
    /foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar
    14 repeats: 13.580000   0.110000  13.690000 ( 13.790675)
    /foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar
    15 repeats: 54.090000   0.210000  54.300000 ( 54.562672)

然后,我尝试了类似于给定here的正则表达式:

re2 = /(\/.+)(?=.*\1)\1\1/

没有性能问题,并匹配我想要匹配的字符串:

re2 === s3 #=> true

但也匹配我不希望它匹配的字符串,例如:

re2 === s4 #=> true, but should be false

我接近第二个正则表达式。我错过了什么?

2 个答案:

答案 0 :(得分:2)

-builmdmode=shared更改为libstd.so。这应该会降低正则表达式的复杂性,因为它不会尝试匹配“任何”字符。

shared

答案 1 :(得分:0)

定义

假设:

str = 'http://www.example.com/dog/baz/biz/baz/biz/baz/biz/cat/'

我们可以将'/dog''/baz''/biz'等定义为 group 由一个或多个连续的段组成,例如'/dog''/baz''/dog/baz''/baz''/baz/biz''biz/baz''/baz/biz/baz'等。

问题

据我了解,问题在于确定给定的字符串是否包含三个(或更多)连续且相等的组,后跟一个正斜杠。 s2符合以下子字符串的要求:

'/baz/biz/baz/biz/baz/biz/'

算法

我不相信可以使用一个正则表达式来确定,但是我们可以编写一个正则表达式来确定是否存在至少三个(或任意数量)连续的,相等的组。 每组的细分数。假设这是通过名为contiguous_fixed_group_size?的方法完成的,该方法如下:

contiguous_fixed_group_size?(str, segments_per_group, nbr_groups)

并返回truefalse。为确保字符串具有至少3个连续的相等组(对于给定值segments_per_group),我们用nbr_groups = 3调用此方法。我认为最好暂时推迟此方法的构建;就目前而言,假设它对我们可用。

我采用的方法是用不同的segments_per_group值调用此方法,并确定该方法是否对于其中至少一个返回true

主要方法

第一步是确定字符串中的段数(其中str保存上面给出的字符串):

 r = /(?<!\/)\/(?!\/)/
 nbr_segments = str.scan(r).size - 1 
   #=> 8

我们可以通过以 free-spacing 模式编写正则表达式来记录该正则表达式:

 r = /
     (?<!\/)  # match is not to be preceded by '/' (negative lookbehind)
     \/       # march '/' 
     (?!\/)   # match is not to be followed by '/' (negative lookahead)
     /x

环顾四周阻止'//'中的str匹配。

我们现在问自己,必须考虑的segments_per_group的最大值是多少。因为我们要求:

nbr_groups * segments_per_group <= nbr_segments

随之而来:

segments_per_group <= nbr_segments/nbr_groups

其中右侧使用整数算术。对于nbr_groups = 3,我们获得:

segments_per_group <= 8/3 => 2

因此,我们可以确定str是否包含(至少)nbr_groups个连续的,相等的组,如下所示:

(1..nbr_segments/nbr_groups).any? do |segs_per_group|
  contiguous_fixed_group_size?(str, segs_per_group, nbr_groups)
end
  #=> true

我们可以将其包装在一个方法中:

def contiguous?(str, nbr_groups)
  nbr_segments = str.scan(/(?<!\/)\/(?!\/)/).size - 1
  (1..nbr_segments/nbr_groups).any? do |segs_per_grp|
    contiguous_fixed_group_size?(str, segs_per_grp, nbr_groups)
  end
end

构造方法contiguous_fixed_group_size?

此方法可以编写如下:

def contiguous_fixed_group_size?(str, segments_per_group, nbr_groups)
  r = /((?:\/[^\/]+){#{segments_per_group}})\1{#{nbr_groups-1}}/ 
  str.match?(r)
end

对于

str = s2
segments_per_group = 2
nbr_groups = 3

正则表达式为:

r #=> /((?:\/[^\/]+){2})\1{2}\//

此处以自由行模式

r = /
    (?<!\/)                    # match is not to be preceded by a forward slash
                               # (negative lookbehind)    
    (                          # begin capture group 1
      (?:                      # begin non-capture group
        \/[^\/]+               # match '/' followed by 1+ char other than '/'
      )                        # end non-capture group 
      {#{segments_per_group}}  # execute non-capture group segments_per_group times
    )                          # end capture group 1
    \1{#{nbr_groups-1}}        # execute contents of capture group 1
                               # nbr_groups-1 times 
    \/                         # match '/'
    /x                         # free-spacing regex definition mode

示例

str如上所述。

contiguous?(str, 3) #=> true
contiguous?(str, 2) #=> true
contiguous?(str, 1) #=> true
contiguous?(str, 4) #=> false

str = 'http://www.example.com/dog/baz/biz/baz/bix/baz/biz/cat/'
contiguous?(str, 3) #=> false
contiguous?(str, 2) #=> false
contiguous?(str, 1) #=> true