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)

Чи можливо це? Або є кращий спосіб, ніж те, як я це роблю?

Відповіді:


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повернеться нуль, тому щось подібне може працювати краще:

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

Хоча scanдля цього мало сенсу. Це все-таки виконує роботу, вам потрібно спочатку розрівняти повернутий масив.one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten


6
Остерігайтеся, що якщо не знайдено відповідностей, матч повертається на нуль, і ви отримуєте NilError. Якщо ви перебуваєте в Rails, я пропоную вам змінитись one, two, three = string.match(/(^.*)(:)(.*)/i).captures на: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Андреа Салікетті

5
@AndreaSalicetti Я відредагував своє повідомлення, я не додаю до нього специфічний для Rails код, тому я змінив його на версію для обробки повернутого нульового об'єкта
Lee Jarvis

3
Ви також можете нового &.оператора повернути його на лінію та навіть використовувати його двічі, коли є лише одна група захоплення. Напр.,string.match(regex)&.captures&.first
Геррі Шоу

46

Ви можете використовувати Match або = ~, що дасть вам один матч, і ви можете або отримати доступ до даних матчу однаковим чином, або просто використовувати спеціальні змінні відповідності

Щось на зразок:

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

5
@Gaston - це насправді оригінальний синтаксис regexp, що походить від Perl :)
ohaleck

28

Ви можете назвати свої захоплені сірники

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

Це не працює, якщо ви переймете порядок рядка та регулярного вираження.


6

Ви повинні вирішити, чи це гарна ідея, але регенекс рубіну може (автоматично) визначати локальні змінні для вас!

Я ще не впевнений, чи є ця функція приголомшливою чи просто абсолютно божевільною, але ваш регекс може визначати локальні змінні.

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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.