Ruby Regexp group matching, asigna variables en 1 línea

125

Actualmente estoy tratando de rexpilar una cadena en múltiples variables. Cadena de ejemplo:

ryan_string = "RyanOnRails: This is a test"

Lo combiné con esta expresión regular, con 3 grupos:

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

Ahora para acceder a cada grupo tengo que hacer algo como esto:

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

Esto parece bastante ridículo y parece que estoy haciendo algo mal. Esperaría poder hacer algo como esto:

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

es posible? ¿O hay una mejor manera de cómo lo estoy haciendo?

ryanjones
fuente

Respuestas:

199

No quieres scanpara esto, ya que tiene poco sentido. Puede usar el String#matchcual devolverá un MatchDataobjeto, luego puede llamar #capturespara devolver una matriz de capturas. Algo como esto:

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

Tenga en cuenta que si no se encuentra ninguna coincidencia, String#matchdevolverá nulo, por lo que algo como esto podría funcionar mejor:

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

Aunque scantiene poco sentido para esto. Todavía hace el trabajo, solo necesita aplanar la matriz devuelta primero.one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten

Lee Jarvis
fuente
66
Tenga en cuenta que si no se encuentran coincidencias, la coincidencia devuelve cero y obtiene un NilError. Si se encuentra en los carriles, lo que sugeriría un cambio: one, two, three = string.match(/(^.*)(:)(.*)/i).captures en: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Andrea Salicetti
55
@AndreaSalicetti He editado mi publicación, no le estoy agregando código específico de Rails, así que la he modificado con una versión para manejar el objeto nulo devuelto
Lee Jarvis
3
También podría &.utilizar el nuevo operador para volver a ponerlo en línea e incluso usarlo dos veces cuando solo hay un grupo de captura. Eg ..,string.match(regex)&.captures&.first
Gerry Shaw
46

Podrías usar Match o = ~ en su lugar, lo que le daría una sola coincidencia y podría acceder a los datos de coincidencia de la misma manera o simplemente usar las variables de coincidencia especiales $ 1, $ 2, $ 3

Algo como:

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end
Rado
fuente
55
@Gaston esa es realmente la sintaxis original de
expresiones regulares
28

Puedes nombrar tus partidos capturados

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

No funciona si invierte el orden de la cadena y la expresión regular.

enviar
fuente
6

¡Debe decidir si es una buena idea, pero ruby ​​regexp puede (automáticamente) definir variables locales para usted!

Todavía no estoy seguro de si esta característica es increíble o simplemente totalmente loca, pero su expresión regular puede definir variables locales.

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

(Eche un vistazo a http://ruby-doc.org/core-2.1.1/Regexp.html , busque "variable local").

Nota: Como se señaló en un comentario, veo que hay una respuesta similar y anterior a esta pregunta por @toonsend ( https://stackoverflow.com/a/21412455 ). No creo que estuviera "robando", pero si quieres ser justo con los elogios y honrar la primera respuesta, siéntete libre :) Espero que ningún animal haya sido dañado.

Felix
fuente
Esta respuesta es notablemente similar a stackoverflow.com/a/21412455/525478 , que es más de un año mayor ...
Brad Werth
@BradWerth Supongo que simplemente no vi eso. Pero actualicé mi respuesta para incluir sus inquietudes.
Felix
5

scan() encontrará todas las coincidencias no superpuestas de la expresión regular en su cadena, por lo que en lugar de devolver una matriz de sus grupos como parece esperar, está devolviendo una matriz de matrices.

Probablemente sea mejor usar match(), y luego obtener la matriz de capturas usando MatchData#captures:

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

Sin embargo, también podría hacer esto scan()si quisiera:

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