2010-06-22 8 views
27

Soy un novato en ruby, quiero saber si puedo usar solo una línea para hacer el trabajo.cómo usar una expresión regular de una línea para obtener contenido coincidente

Tome la "búsqueda" de este sitio, por ejemplo. Cuando usuario escribió [ruby] regex, puedo usar siguiente código para obtener la etiqueta y la palabra clave

'[ruby] regex' =~ /\[(.*?)\](.*)/ 
tag, keyword = $1, $2 

Podemos escribir sólo en una línea?


ACTUALIZACIÓN

Muchas gracias! ¿Puedo hacer más difícil y más interesante, que la entrada puede contener más de un tag, como:

[ruby] [regex] [rails] one line 

¿Es posible utilizar una línea de código para obtener la matriz de etiquetas y la palabra clave? Lo intenté, pero fallé.

+1

Para la actualización: si quisiera hacer esto en una sola expresión regular, necesitaría el motor de expresiones regulares .NET o Perl 6, actualmente los únicos que admiten capturas dentro de elementos repetidos. Entonces, con IronRuby, probablemente tendrías una oportunidad. Ver también http://stackoverflow.com/questions/2652554/which-regex-flavors-support-captures-as-opposed-to-capturing-groups - sin embargo, para la legibilidad y la mantenibilidad, un enfoque de dos pasos es probablemente más sensato . –

Respuesta

41

Necesita el método Regexp#match. Si escribe /\[(.*?)\](.*)/.match('[ruby] regex'), esto devolverá un objeto MatchData. Si llamamos a ese objeto matches, entonces, entre otras cosas:

  • matches[0] devuelve toda la cadena coincidente.
  • matches[n] devuelve el enésimo grupo de captura ($n).
  • matches.to_a devuelve una matriz que consiste en matches[0] hasta matches[N].
  • matches.captures devuelve una matriz que consiste en solo el grupo de captura (matches[1] a matches[N]).
  • matches.pre_match devuelve todo antes de la cadena coincidente.
  • matches.post_match devuelve todo después de la cadena coincidente.

Existen más métodos, que corresponden a otras variables especiales, etc .; Puede marcar MatchData's docs para más. Por lo tanto, en este caso específico, todo lo que necesita para escribir es

tag, keyword = /\[(.*?)\](.*)/.match('[ruby] regex').captures 

Edición 1: bien, para su tarea más difícil, usted va a querer en lugar del método String#scan, que @Theo utilizado; sin embargo, vamos a usar una expresión regular diferente. El siguiente código debería funcionar:

# You could inline the regex, but comments would probably be nice. 
tag_and_text =/\[([^\]]*)\] # Match a bracket-delimited tag, 
       \s*   # ignore spaces, 
       ([^\[]*) /x # and match non-tag search text. 
input  = '[ruby] [regex] [rails] one line [foo] [bar] baz' 
tags, texts = input.scan(tag_and_text).transpose 

la input.scan(tag_and_text) devolverá una lista de pares de marcado y búsqueda de texto:

[ ["ruby", ""], ["regex", ""], ["rails", "one line "] 
, ["foo", ""], ["bar", "baz"] ] 

La llamada transpose voltea que, para que tenga un par que consiste en una etiqueta lista y una lista de texto de búsqueda:

[["ruby", "regex", "rails", "foo", "bar"], ["", "", "one line ", "", "baz"]] 

A continuación, puede hacer lo que quiera con los resultados. Que podría sugerir, por ejemplo

search_str = texts.join(' ').strip.gsub(/\s+/, ' ') 

Esto concatenar los fragmentos de búsqueda con espacios individuales, deshacerse de espacio inicial y final, y vuelva a colocar carreras de espacios múltiples con un solo espacio.

11
'[ruby] regex'.scan(/\[(.*?)\](.*)/) 

volverá

[["ruby", " regex"]] 

se puede leer más acerca de cuerda # exploración aquí: http://ruby-doc.org/core/classes/String.html#M000812 (en resumen, devuelve una matriz de todos los partidos consecutivos, la matriz externa en este caso es la matriz de coincidencias , y el interior es los grupos de captura de un partido).

hacer la tarea puede volver a escribir como este (suponiendo que usted sólo tenga una coincidencia en la cadena):

tag, keyword = '[ruby] regex'.scan(/\[(.*?)\](.*)/).flatten 

dependiendo exactamente lo que quiere lograr es posible que desee cambiar la expresión regular a

/^\s*\[(.*?)\]\s*(.+)\s*$/ 

que coincide con toda la cadena de entrada y recorta algunos espacios del segundo grupo de captura. Anclar el patrón al principio y al final lo hará un poco más eficiente, y evitará obtener coincidencias falsas o duplicadas en algunos casos (pero eso depende en gran medida de la información ingresada); también garantiza que puede usar de manera segura los datos devueltos. array en la asignación, porque nunca tendrá más de una coincidencia.

En cuanto a la pregunta de seguimiento, esto es lo que haría:

def tags_and_keyword(input) 
    input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) do |match| 
    tags = match[0].split(/\]\s*\[/) 
    line = match[1] 
    return tags, line 
    end 
end 

tags, keyword = tags_and_keyword('[ruby] [regex] [rails] one line') 
tags # => ["ruby", "regex", "rails"] 
keyword # => "one line" 

que se puede reescribir en una línea, pero no me:

tags, keyword = catch(:match) { input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) { |match| throw :match, [match[0].split(/\]\s*\[/), match[1]] } } 

Mi solución asume toda las etiquetas aparecen antes que la palabra clave, y solo hay una expresión de etiquetas/palabras clave en cada entrada. La primera captura engloba todas las etiquetas, pero luego divido esa cadena, por lo que es un proceso de dos pasos (que, como @Tim escribió en su comentario, es obligatorio a menos que tengas un motor capaz de hacer coincidir recursivamente).

Cuestiones relacionadas