Dopasowanie grupy Ruby Regexp, przypisz zmienne w 1 linii

125

Obecnie próbuję przekształcić ciąg na wiele zmiennych. Przykładowy ciąg:

ryan_string = "RyanOnRails: This is a test"

Dopasowałem to do tego wyrażenia regularnego z trzema grupami:

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

Teraz, aby uzyskać dostęp do każdej grupy, muszę zrobić coś takiego:

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

Wydaje się to dość śmieszne i wydaje mi się, że robię coś złego. Spodziewałbym się, że będę mógł zrobić coś takiego:

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

czy to możliwe? A może jest lepszy sposób niż to, jak to robię?

ryanjones
źródło

Odpowiedzi:

199

Nie chcesz scantego, ponieważ nie ma to sensu. Możesz użyć, String#matchktóry zwróci MatchDataobiekt, możesz następnie wywołać, #capturesaby zwrócić tablicę przechwyceń. Coś takiego:

#!/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"

Pamiętaj, że jeśli nie zostanie znalezione dopasowanie, String#matchzwróci nil, więc coś takiego może działać lepiej:

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

Chociaż scannie ma to większego sensu. Nadal wykonuje swoje zadanie, wystarczy najpierw spłaszczyć zwróconą tablicę.one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten

Lee Jarvis
źródło
6
Uważaj, jeśli nie zostaną znalezione żadne dopasowania, dopasowanie zwraca nil, a otrzymasz NilError. Jeśli jesteś w Railsach, proponuję zmienić: one, two, three = string.match(/(^.*)(:)(.*)/i).captures na: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Andrea Salicetti
5
@AndreaSalicetti Edytowałem swój post, nie dodam do niego kodu specyficznego dla Railsów, więc zmieniłem go z wersją do obsługi zwróconego obiektu zerowego
Lee Jarvis
3
Możesz także nowy &.operator, aby przywrócić go do linii, a nawet użyć go dwukrotnie, gdy jest tylko jedna grupa przechwytywania. Np. ...,string.match(regex)&.captures&.first
Gerry Shaw
46

Możesz zamiast tego użyć Match lub = ~, co da ci jedno dopasowanie i możesz albo uzyskać dostęp do danych dopasowania w ten sam sposób, albo po prostu użyć specjalnych zmiennych dopasowania $ 1, $ 2, $ 3

Coś jak:

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end
Rado
źródło
5
@Gaston to właściwie oryginalna składnia
wyrażenia regularnego
28

Możesz nazwać przechwycone mecze

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

Nie działa, jeśli odwrócisz kolejność ciągów i wyrażeń regularnych.

toonsend
źródło
6

Musisz zdecydować, czy to dobry pomysł, ale wyrażenie regularne Ruby może (automagicznie) definiować zmienne lokalne za Ciebie!

Nie jestem jeszcze pewien, czy ta funkcja jest niesamowita, czy po prostu całkowicie szalona, ​​ale twoje wyrażenie regularne może definiować zmienne lokalne.

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}"

(Spójrz na http://ruby-doc.org/core-2.1.1/Regexp.html , wyszukaj „zmienna lokalna”).

Uwaga: Jak wskazano w komentarzu, widzę, że istnieje podobna i wcześniejsza odpowiedź na to pytanie od @toonsend ( https://stackoverflow.com/a/21412455 ). Chyba nie „kradłem”, ale jeśli chcesz być uczciwy w pochwałach i uhonorować pierwszą odpowiedź, nie krępuj się :) Mam nadzieję, że żadne zwierzęta nie zostały skrzywdzone.

Felix
źródło
Ta odpowiedź wygląda niezwykle podobnie do stackoverflow.com/a/21412455/525478 , który jest o ponad rok starszy ...
Brad Werth
@BradWerth Chyba po prostu tego nie widziałem. Ale zaktualizowałem moją odpowiedź, aby uwzględnić Twoje obawy.
Felix
5

scan() znajdzie wszystkie nienakładające się dopasowania wyrażenia regularnego w twoim ciągu, więc zamiast zwracać tablicę twoich grup, jak się wydaje, że się spodziewasz, zwraca tablicę tablic.

Prawdopodobnie lepiej będzie, jeśli użyjesz match(), a następnie uzyskasz tablicę przechwytywania za pomocą MatchData#captures:

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

Jednak możesz to również zrobić, scan()jeśli chcesz:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)[0]
Andrew Clark
źródło