Сопоставление групп Ruby Regexp, присвоение переменных в 1 строке

125

В настоящее время я пытаюсь преобразовать строку в несколько переменных. Пример строки:

ryan_string = "RyanOnRails: This is a test"

Я сопоставил его с этим регулярным выражением с 3 группами:

ryan_group = ryan_string.scan(/(^.*)(:)(.*)/i)

Теперь, чтобы получить доступ к каждой группе, мне нужно сделать что-то вроде этого:

ryan_group[0][0] (first group) RyanOnRails
ryan_group[0][1] (second group) :
ryan_group[0][2] (third group) This is a test

Это кажется довольно смешным, и мне кажется, что я делаю что-то не так. Я ожидал, что смогу сделать что-то вроде этого:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)

Это возможно? Или есть способ лучше, чем я?

ryanjones
источник

Ответы:

199

Вы этого не хотите scan, потому что в этом мало смысла. Вы можете использовать, String#matchкоторый вернет MatchDataобъект, затем вы можете вызвать, #capturesчтобы вернуть массив захватов. Что-то вроде этого:

#!/usr/bin/env ruby

string = "RyanOnRails: This is a test"
one, two, three = string.match(/(^.*)(:)(.*)/i).captures

p one   #=> "RyanOnRails"
p two   #=> ":"
p three #=> " This is a test"

Имейте в виду, что если совпадение не найдено, String#matchвернет nil, поэтому что-то вроде этого может работать лучше:

if match = string.match(/(^.*)(:)(.*)/i)
  one, two, three = match.captures
end

Хотя scanдействительно в этом мало смысла. Он по-прежнему выполняет свою работу, вам просто нужно сначала сгладить возвращенный массив.one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten

Ли Джарвис
источник
6
Помните, что если совпадений не найдено, match возвращает nil, и вы получаете NilError. Если вы работаете в Rails, я предлагаю вам заменить: one, two, three = string.match(/(^.*)(:)(.*)/i).captures на: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Андреа Салицетти
5
@AndreaSalicetti Я отредактировал свой пост, я не добавляю в него код, специфичный для Rails, поэтому я изменил его версией для обработки возвращенного объекта nil
Ли Джарвис
3
Вы также можете использовать новый &.оператор, чтобы вернуть его в линию и даже использовать его дважды, когда есть только одна группа захвата. Например,string.match(regex)&.captures&.first
Джерри Шоу
46

Вы можете использовать MatchВместо этого или = ~, что даст вам одно совпадение, и вы можете получить доступ к данным совпадения таким же образом или просто использовать специальные переменные совпадения $ 1, $ 2, $ 3

Что-то вроде:

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end
Rado
источник
5
@Gaston, это на самом деле оригинальный синтаксис регулярного выражения, происходящий из Perl :)
ohaleck
28

Вы можете назвать свои захваченные матчи

string = "RyanOnRails: This is a test"
/(?<one>^.*)(?<two>:)(?<three>.*)/i =~ string
puts one, two, three

Это не сработает, если вы измените порядок строки и регулярного выражения.

toonsend
источник
6

Вы должны решить, хорошая ли это идея, но регулярное выражение ruby ​​может (автоматически) определять локальные переменные за вас!

Я еще не уверен, хороша ли эта функция или просто сумасшедшая, но ваше регулярное выражение может определять локальные переменные.

ryan_string = "RyanOnRails: This is a test"
/^(?<webframework>.*)(?<colon>:)(?<rest>)/ =~ ryan_string
# This defined three variables for you. Crazy, but true.
webframework # => "RyanOnRails"
puts "W: #{webframework} , C: #{colon}, R: #{rest}"

(Взгляните на http://ruby-doc.org/core-2.1.1/Regexp.html , найдите "локальную переменную").

Примечание. Как указано в комментарии, я вижу, что на этот вопрос есть аналогичный и более ранний ответ от @toonsend ( https://stackoverflow.com/a/21412455 ). Я не думаю, что я «воровал», но если вы хотите быть честными с похвалами и уважать первый ответ, не стесняйтесь :) Я надеюсь, что ни одно животное не пострадало.

Феликс
источник
Этот ответ очень похож на stackoverflow.com/a/21412455/525478 , который старше на год ...
Брэд Верт,
@BradWerth Думаю, я просто этого не заметил. Но я обновил свой ответ, включив в него ваши опасения.
Феликс
5

scan() найдет все неперекрывающиеся совпадения регулярного выражения в вашей строке, поэтому вместо того, чтобы возвращать массив ваших групп, как вы, кажется, ожидаете, он возвращает массив массивов.

Вам, вероятно, лучше использовать match(), а затем получить массив захватов, используя MatchData#captures:

g1, g2, g3 = ryan_string.match(/(^.*)(:)(.*)/i).captures

Однако вы также можете сделать это, scan()если хотите:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)[0]
Эндрю Кларк
источник