regulares regular rails expressions expresiones examples ruby regex

regular - Regex con grupos de captura nombrados obteniendo todas las coincidencias en Ruby



rubular (10)

@Nakilon es correcto mostrando el scan con una expresión regular, sin embargo, ni siquiera es necesario aventurarse en la tierra de expresiones regulares si no desea:

s = "123--abc,123--abc,123--abc" s.split('','') #=> ["123--abc", "123--abc", "123--abc"] s.split('','').inject([]) { |a,s| a << s.split(''--''); a } #=> [["123", "abc"], ["123", "abc"], ["123", "abc"]]

Esto devuelve una matriz de matrices, lo que es conveniente si tiene varias apariciones y necesita verlas / procesarlas todas.

s.split('','').inject({}) { |h,s| n,v = s.split(''--''); h[n] = v; h } #=> {"123"=>"abc"}

Esto devuelve un hash, que, debido a que los elementos tienen la misma clave, solo tiene el valor de la clave única. Esto es bueno cuando tienes un montón de claves duplicadas pero quieres las únicas. Su inconveniente ocurre si necesita los valores únicos asociados con las claves, pero esa parece ser una pregunta diferente.

Tengo una cadena:

s="123--abc,123--abc,123--abc"

Intenté usar la nueva característica de Ruby 1.9 "grupos con nombre" para obtener toda la información del grupo con nombre:

/(?<number>/d*)--(?<chars>/s*)/

¿Existe una API como findall de Python que devuelve una colección de datos de matchdata ? En este caso, necesito devolver dos coincidencias, porque 123 y abc repiten dos veces. Cada dato de coincidencia contiene detalles de cada información de captura nombrada, así que puedo usar m[''number''] para obtener el valor de coincidencia.


Aprovechando la respuesta de Mark Hubbart, agregué el siguiente parche de mono:

class ::Regexp def match_all(str) matches = [] str.scan(self) { matches << $~ } matches end end

que se puede usar como /(?<letter>/w)/.match_all(''word'') , y devuelve:

[#<MatchData "w" letter:"w">, #<MatchData "o" letter:"o">, #<MatchData "r" letter:"r">, #<MatchData "d" letter:"d">]

Esto se basa en, como han dicho otros, el uso de $~ en el bloque de exploración para los datos de coincidencia.


Entrando súper tarde, pero aquí hay una forma sencilla de replicar el escaneo de String # pero obteniendo los datos de coincidencia en su lugar:

matches = [] foo.scan(regex){ matches << $~ }

matches ahora contiene los objetos MatchData que corresponden a escanear la cadena.


Hace un año quería expresiones regulares que fueran más fáciles de leer y nombraran las capturas, así que hice la siguiente adición a Cadena (tal vez no debería estar allí, pero era conveniente en ese momento):

scan2.rb:

class String #Works as scan but stores the result in a hash indexed by variable/constant names (regexp PLACEHOLDERS) within parantheses. #Example: Given the (constant) strings BTF, RCVR and SNDR and the regexp /#BTF# (#RCVR#) (#SNDR#)/ #the matches will be returned in a hash like: match[:RCVR] = <the match> and match[:SNDR] = <the match> #Note: The #STRING_VARIABLE_OR_CONST# syntax has to be used. All occurences of #STRING# will work as #{STRING} #but is needed for the method to see the names to be used as indices. def scan2(regexp2_str, mark=''#'') regexp = regexp2_str.to_re(mark) #Evaluates the strings. Note: Must be reachable from here! hash_indices_array = regexp2_str.scan(//(#{mark}(.*?)#{mark}/)/).flatten #Look for string variable names within (#VAR#) or # replaced by <mark> match_array = self.scan(regexp) #Save matches in hash indexed by string variable names: match_hash = Hash.new match_array.flatten.each_with_index do |m, i| match_hash[hash_indices_array[i].to_sym] = m end return match_hash end def to_re(mark=''#'') re = /#{mark}(.*?)#{mark}/ return Regexp.new(self.gsub(re){eval $1}, Regexp::MULTILINE) #Evaluates the strings, creates RE. Note: Variables must be reachable from here! end end

Ejemplo de uso (irb1.9):

> load ''scan2.rb'' > AREA = ''/d+'' > PHONE = ''/d+'' > NAME = ''/w+'' > "1234-567890 Glenn".scan2(''(#AREA#)-(#PHONE#) (#NAME#)'') => {:AREA=>"1234", :PHONE=>"567890", :NAME=>"Glenn"}

Notas:

Por supuesto, hubiera sido más elegante colocar los patrones (por ejemplo, ÁREA, TELÉFONO ...) en un hash y agregar este hash con patrones a los argumentos de scan2.


Las capturas con nombre son adecuadas solo para un resultado coincidente.
El análogo de Ruby de findall es String#scan . Puede usar el resultado del scan como una matriz, o pasarle un bloque:

irb> s = "123--abc,123--abc,123--abc" => "123--abc,123--abc,123--abc" irb> s.scan(/(/d*)--([a-z]*)/) => [["123", "abc"], ["123", "abc"], ["123", "abc"]] irb> s.scan(/(/d*)--([a-z]*)/) do |number, chars| irb* p [number,chars] irb> end ["123", "abc"] ["123", "abc"] ["123", "abc"] => "123--abc,123--abc,123--abc"


Me gusta el match_all dado por John, pero creo que tiene un error.

La línea:

match_datas << md

Funciona si no hay capturas () en la expresión regular.

Este código da toda la línea hasta e incluyendo el patrón coincidente / capturado por la expresión regular. (La parte [0] de MatchData) Si la expresión regular tiene capture (), este resultado probablemente no sea lo que el usuario (yo) quiere en la salida final.

Creo que en el caso donde hay captures () en expresiones regulares, el código correcto debería ser:

match_datas << md[1]

La salida final de match_datas será una matriz de coincidencias de captura de patrones a partir de match_datas [0]. Esto no es exactamente lo que puede esperarse si se desea un MatchData normal que incluya un valor match_datas [0] que es la subcadena completa seguida de match_datas [1], match_datas [[2], .. cuáles son las capturas (si las hay) ) en el patrón regex.

Las cosas son complejas, por lo que match_all no se incluyó en MatchData nativo.


Necesitaba algo similar recientemente. Esto debería funcionar como String#scan , pero en cambio devuelve una matriz de objetos MatchData.

class String # This method will return an array of MatchData''s rather than the # array of strings returned by the vanilla `scan`. def match_all(regex) match_str = self match_datas = [] while match_str.length > 0 do md = match_str.match(regex) break unless md match_datas << md match_str = md.post_match end return match_datas end end

Ejecutar sus datos de muestra en los resultados de REPL en lo siguiente:

> "123--abc,123--abc,123--abc".match_all(/(?<number>/d*)--(?<chars>[a-z]*)/) => [#<MatchData "123--abc" number:"123" chars:"abc">, #<MatchData "123--abc" number:"123" chars:"abc">, #<MatchData "123--abc" number:"123" chars:"abc">]

También puede encontrar útil mi código de prueba:

describe String do describe :match_all do it "it works like scan, but uses MatchData objects instead of arrays and strings" do mds = "ABC-123, DEF-456, GHI-098".match_all(/(?<word>[A-Z]+)-(?<number>[0-9]+)/) mds[0][:word].should == "ABC" mds[0][:number].should == "123" mds[1][:word].should == "DEF" mds[1][:number].should == "456" mds[2][:word].should == "GHI" mds[2][:number].should == "098" end end end


Puede extraer las variables utilizadas de la expresión regular utilizando el método de names . Entonces, lo que hice fue usar un método de scan regular para obtener las coincidencias, luego los nombres comprimidos y cada coincidencia para crear un Hash .

class String def scan2(regexp) names = regexp.names scan(regexp).collect do |match| Hash[names.zip(match)] end end end

Uso:

>> "aaa http://www.google.com.tr aaa https://www.yahoo.com.tr ddd".scan2 /(?<url>(?<protocol>https?):////[/S]+)/ => [{"url"=>"http://www.google.com.tr", "protocol"=>"http"}, {"url"=>"https://www.yahoo.com.tr", "protocol"=>"https"}]


Realmente me gustó la solución de @ Umut-Utkan, pero no hizo lo que quería, así que la reescribí un poco (nota, el código de abajo puede no ser hermoso, pero parece funcionar)

class String def scan2(regexp) names = regexp.names captures = Hash.new scan(regexp).collect do |match| nzip = names.zip(match) nzip.each do |m| captgrp = m[0].to_sym captures.add(captgrp, m[1]) end end return captures end end

Ahora si lo haces

p ''12f3g4g5h5h6j7j7j''.scan2(/(?<alpha>[a-zA-Z])(?<digit>[0-9])/)

Usted obtiene

{:alpha=>["f", "g", "g", "h", "h", "j", "j"], :digit=>["3", "4", "5", "5", "6", "7", "7"]}

(es decir, todos los caracteres alfa encontrados en una matriz y todos los dígitos encontrados en otra matriz). Dependiendo de su propósito para escanear, esto podría ser útil. De todos modos, me encanta ver ejemplos de lo fácil que es reescribir o extender la funcionalidad central de Ruby con solo unas pocas líneas.


Si usa ruby> = 1.9 y las capturas nombradas, podría:

class String def scan2(regexp2_str, placeholders = {}) return regexp2_str.to_re(placeholders).match(self) end def to_re(placeholders = {}) re2 = self.dup separator = placeholders.delete(:SEPARATOR) || '''' #Returns and removes separator if :SEPARATOR is set. #Search for the pattern placeholders and replace them with the regex placeholders.each do |placeholder, regex| re2.sub!(separator + placeholder.to_s + separator, "(?<#{placeholder}>#{regex})") end return Regexp.new(re2, Regexp::MULTILINE) #Returns regex using named captures. end end

Uso (rubí> = 1.9):

> "1234:Kalle".scan2("num4:name", num4:''/d{4}'', name:''/w+'') => #<MatchData "1234:Kalle" num4:"1234" name:"Kalle">

o

> re="num4:name".to_re(num4:''/d{4}'', name:''/w+'') => /(?<num4>/d{4}):(?<name>/w+)/m > m=re.match("1234:Kalle") => #<MatchData "1234:Kalle" num4:"1234" name:"Kalle"> > m[:num4] => "1234" > m[:name] => "Kalle"

Usando la opción de separador:

> "1234:Kalle".scan2("#num4#:#name#", SEPARATOR:''#'', num4:''/d{4}'', name:''/w+'') => #<MatchData "1234:Kalle" num4:"1234" name:"Kalle">