Подтвердить что ты не робот

Как использовать однострочное регулярное выражение для получения согласованного содержимого

Я новичок в рубине, я хочу знать, могу ли я использовать только одну строку для выполнения этой работы.

Возьмите "поиск" этого сайта, например. Когда пользователь набрал [ruby] regex, я могу использовать следующий код для получения тега и ключевого слова

'[ruby] regex' =~ /\[(.*?)\](.*)/
tag, keyword = $1, $2

Можем ли мы написать его только в одной строке?


UPDATE

Большое вам спасибо! Могу ли я сделать это сложнее и интереснее, что вход может содержать более одного тега, например:

[ruby] [regex] [rails] one line

Можно ли использовать один код строки для получения массива меток и ключевого слова? Я попытался, но не смог.

4b9b3361

Ответ 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 в своем комментарии, требуется, если у вас нет механизма, способного к рекурсивному сопоставлению).