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

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

ryan_string = "RyanOnRails: This is a test"

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

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)

Возможно ли это? Или есть лучший способ, чем то, как я это делаю?

Ответ 1

Вы не хотите 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 вернет нуль, значит, что-то вроде этого может работать лучше:

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

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

Ответ 2

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

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

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end

Ответ 3

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

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

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

Ответ 4

Вы должны решить, является ли это хорошей идеей, но регулярное выражение 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 (fooobar.com/questions/66890/...). Я не думаю, что я "воровал", но если вы хотите быть честным с похвалой и чествовать первый ответ, не стесняйтесь :) Я надеюсь, что никто из животных не пострадал.

Ответ 5

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

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

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

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

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

Ответ 6

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

  "hello world".match /(hello)(\s)(world)/ # => #<MatchData "hello world" 1:"hello" 2:" " 3:"world">
  $1 # => "hello"
  $2 # => " "
  $3 # => "world"
  $4 # => nil
  "hello #{$3}" # => "hello world"