如何在一行中编写正则表达式

时间:2016-03-24 21:11:07

标签: ruby regex

我有这段代码:

str = 'printf("My name is %s and age is %0.2d", name, age);'

SPECIFIERS = 'diuXxofeEgsc'
format_specifiers  = /((?:%(?:\*?([-+]?\d*\.?\d+)*(?:[#{SPECIFIERS}]))))/i

variables = /([.[^"]]*)\);$/

format = str.scan(format_specifiers)
var = str.scan(variables).first.first.split(/,/)

单个正则表达式有没有办法在几行中做到这一点?

我想要的输出是:

%s,  name
%0.2d,  age

2 个答案:

答案 0 :(得分:1)

我非常相信保持正则表达式尽可能简单;他们可能会迅速陷入笨拙/不可维护的困境中。我会从这样的事情开始,然后根据需要进行调整:

str = 'printf("My name is %s and age is %0.2d", name, age);'

formats = str.scan(/%[a-z0-9.]+/) # => ["%s", "%0.2d"]

str[/,(.+)\);$/] # => ", name, age);"
vars = str[/,(.+)\);$/].scan(/[a-z]+/) # => ["name", "age"]

puts formats.zip(vars).map{ |a| a.join(', ')}

# >> %s, name
# >> %0.2d, age

答案 1 :(得分:1)

您的问题分为两部分:

  • Q1:是否可以使用单个正则表达式执行此操作?
  • Q2:可以用一行或两行代码完成吗?

这两个问题的答案都是“是”。

format_specifiers = /
                    %[^\s\"\z]+  # match % followed by > 0 characters other than a
                                 # whitespace, a double-quote or the end of the string
                    /x           # free-spacing regex definition mode

variables         = /
                    ,\s*         # match comma followed by >= 0 whitespaces 
                    \K           # forget matches so far
                    [a-z]        # match a lowercase letter
                    \w*          # match >= 0 word characters
                    /x

您可以在测试后决定这两个正则表达式是否能够充分发挥作用。有关测试,请参阅Kernel#sprintf

 r = /
     (?:#{format_specifiers})    # match format_specifiers in a non-capture group
     |                           # or
     (?:#{variables})            # match variables in a non-capture group
     /x

     #=> /
         (?:(?x-mi:
           %[^\s\"\z]+ # match % followed by > 0 characters other than a
                       # whitespace, a double-quote or the end of the string
           ))          # match format_specifiers in a non-capture group
         |             # or
         (?:(?x-mi:
           ,\s*        # match comma followed by >= 0 whitespaces 
           \K          # forget matches so far
           [a-zA-Z]    # match a letter
           \w*         # match >= 0 word characters
         ))            # match variables in a non-capture group
         /x

r当然也可以写成:

/(?:(?x-mi:%[^\s\"\z]+))|(?:(?x-mi:,\s*\K[a-zA-Z]\w*))/ 

从两个正则表达式构造r的一个优点是后者中的每一个都可以单独测试。

str = 'printf("My name is %s and age is %0.2d", name, age);'

arr = str.scan(r)
  #=> ["%s", "%0.2d", "name", "age"] 
arr.each_slice(arr.size/2).to_a.transpose.map { |s| s.join(',  ') }
  #=> ["%s,  name", "%0.2d,  age"]

我有五行代码。我们可以通过简单地替换r中的str.scan(r)来将其减少为2。我们可以写一下这一行:

str.scan(r).tap { |a|
  a.replace(a.each_slice(a.size/2).to_a.transpose.map { |s| s.join(',  ') }) }
  #=> ["%s,  name", "%0.2d,  age"]

替换r

这里的步骤如下:

a = str.scan(r)
  #=> ["%s", "%0.2d", "name", "age"] 
b = a.each_slice(a.size/2)
  #=> a.each_slice(2)
  #=> #<Enumerator: ["%s", "%0.2d", "name", "age"]:each_slice(2)> 
c = b.to_a
  #=> [["%s", "%0.2d"], ["name", "age"]] 
d = c.transpose
  #=> [["%s", "name"], ["%0.2d", "age"]] 
e = d.map { |s| s.join(',  ') }
  #=> ["%s,  name", "%0.2d,  age"] 
a.replace(e)
  #=> ["%s,  name", "%0.2d,  age"]

使用的方法(Array#size除外)是String#scanEnumerable#each_sliceEnumerable#to_aEnumerable#mapArray#transpose和{{3} }。