Как использовать однострочное регулярное выражение для получения согласованного содержимого
Я новичок в рубине, я хочу знать, могу ли я использовать только одну строку для выполнения этой работы.
Возьмите "поиск" этого сайта, например. Когда пользователь набрал [ruby] regex
, я могу использовать следующий код для получения тега и ключевого слова
'[ruby] regex' =~ /\[(.*?)\](.*)/
tag, keyword = $1, $2
Можем ли мы написать его только в одной строке?
UPDATE
Большое вам спасибо! Могу ли я сделать это сложнее и интереснее, что вход может содержать более одного тега, например:
[ruby] [regex] [rails] one line
Можно ли использовать один код строки для получения массива меток и ключевого слова? Я попытался, но не смог.
Ответы
Ответ 1
Вам нужен метод Regexp#match
. Если вы напишете /\[(.*?)\](.*)/.match('[ruby] regex')
, это вернет объект MatchData
. Если мы назовем этот объект matches
, то, между прочим:
-
matches[0]
возвращает всю согласованную строку.
-
matches[n]
возвращает n-ю группу захвата ($n
).
-
matches.to_a
возвращает массив, состоящий из matches[0]
через matches[n]
.
-
matches.captures
возвращает массив, состоящий только из группы захвата (matches[1]
через matches[n]
).
-
matches.pre_match
возвращает все перед совпадающей строкой.
-
matches.post_match
возвращает все после согласованной строки.
Существует больше методов, которые соответствуют другим специальным переменным и т.д.; вы можете проверить MatchData
docs для получения дополнительной информации. Таким образом, в этом конкретном случае все, что вам нужно написать, это
tag, keyword = /\[(.*?)\](.*)/.match('[ruby] regex').captures
Изменить 1: Хорошо, для вашей более сложной задачи вы захотите использовать метод String#scan
, который @Theo используемый; однако мы будем использовать другое регулярное выражение. Следующий код должен работать:
# You could inline the regex, but comments would probably be nice.
tag_and_text = / \[([^\]]*)\] # Match a bracket-delimited tag,
\s* # ignore spaces,
([^\[]*) /x # and match non-tag search text.
input = '[ruby] [regex] [rails] one line [foo] [bar] baz'
tags, texts = input.scan(tag_and_text).transpose
input.scan(tag_and_text)
вернет список пар тегов-поиска-текста:
[ ["ruby", ""], ["regex", ""], ["rails", "one line "]
, ["foo", ""], ["bar", "baz"] ]
Вызов transpose
переворачивается так, что у вас есть пара, состоящая из списка тегов и списка поиска:
[["ruby", "regex", "rails", "foo", "bar"], ["", "", "one line ", "", "baz"]]
Затем вы можете делать все, что хотите, с результатами. Я мог бы предложить, например,
search_str = texts.join(' ').strip.gsub(/\s+/, ' ')
Это объединит фрагменты поиска с единичными пробелами, избавится от начального и конечного пробелов и заменит пробеги нескольких пространств на единое пространство.
Ответ 2
'[ruby] regex'.scan(/\[(.*?)\](.*)/)
вернет
[["ruby", " regex"]]
вы можете узнать больше о String # scan здесь: http://ruby-doc.org/core/classes/String.html#M000812 (вкратце он возвращает массив всех последовательных совпадений, внешний массив в этом случае это массив совпадений, а внутренний - группы захвата одного совпадения).
чтобы выполнить задание, вы можете переписать его следующим образом (предполагая, что в строке будет только одно совпадение):
tag, keyword = '[ruby] regex'.scan(/\[(.*?)\](.*)/).flatten
в зависимости от того, что вы хотите выполнить, вы можете изменить регулярное выражение на
/^\s*\[(.*?)\]\s*(.+)\s*$/
который соответствует всей входной строке и обрезает некоторые пробелы из второй группы захвата. Закрепление шаблона в начале и в конце сделает его более эффективным, и в некоторых случаях он будет избегать ложных или дублирующих совпадений (но это очень зависит от ввода) - это также гарантирует, что вы можете безопасно использовать возвращенный массив в присваивании, потому что он никогда не будет иметь более одного соответствия.
Что касается последующего вопроса, я бы это сделал:
def tags_and_keyword(input)
input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) do |match|
tags = match[0].split(/\]\s*\[/)
line = match[1]
return tags, line
end
end
tags, keyword = tags_and_keyword('[ruby] [regex] [rails] one line')
tags # => ["ruby", "regex", "rails"]
keyword # => "one line"
его можно переписать в одну строку, но я бы не стал:
tags, keyword = catch(:match) { input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) { |match| throw :match, [match[0].split(/\]\s*\[/), match[1]] } }
Мое решение предполагает, что все теги появляются перед ключевым словом и что на каждом входе есть только одно выражение тега/ключевое слово. Первый захват захватывает все теги, но потом я разделяю эту строку, поэтому это двухэтапный процесс (который, как писал @Tim в своем комментарии, требуется, если у вас нет механизма, способного к рекурсивному сопоставлению).