2016-03-24 6 views
1

У меня есть этот код:Как написать регулярное выражение в одной строке

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 
+0

Как вы думаете, '/([.[^"]]*)\);$/' означает в regex-ese? –

+0

Это означает начало с конца строки и все, если не найдено '' ' поэтому после сканирования он вернет ', name, age', означает, что он даст все переменные или даже уравнения (например,' a + b'), чтобы я мог сопоставить их с их спецификатором требуемого формата. –

ответ

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

▲ Второе, что вы сказали о RegEx, а также ваше решение. Не могли бы вы проверить мою [regex101 demo] (https://regex101.com/r/lY1kY2/1)? Я почти всегда использую RegEx. ** Это может быть против вашей веры. ** –

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). Мы могли бы сделать это в одну строку, написав:

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#scan, Enumerable#each_slice, Enumerable#to_a, Enumerable#map, Array#transpose и Array#replace.

Смежные вопросы