Ваш вопрос состоит из двух частей:
- 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.
Как вы думаете, '/([.[^"]]*)\);$/' означает в regex-ese? –
Это означает начало с конца строки и все, если не найдено '' ' поэтому после сканирования он вернет ', name, age', означает, что он даст все переменные или даже уравнения (например,' a + b'), чтобы я мог сопоставить их с их спецификатором требуемого формата. –