strings scan method funcion example contar comparar caracteres cadenas ruby parsing tokenize text-parsing

scan - ¿Cómo puedo tokenizar esta cadena en Ruby?



scan ruby example (3)

Aquí hay un ejemplo no robusto usando StringScanner . Este es el código que acabo de adaptar de Ruby Quiz: Parsing JSON , que tiene una excelente explicación.

require ''strscan'' def test_parse text = %{Children^10 Health "sanitation management"^5} expected = [{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}] assert_equal(expected, parse(text)) end def parse(text) @input = StringScanner.new(text) output = [] while keyword = parse_string || parse_quoted_string output << { :keywords => keyword, :boost => parse_boost } trim_space end output end def parse_string if @input.scan(//w+/) @input.matched.downcase else nil end end def parse_quoted_string if @input.scan(/"/) str = parse_quoted_contents @input.scan(/"/) or raise "unclosed string" str else nil end end def parse_quoted_contents @input.scan(/[^//"]+/) and @input.matched end def parse_boost if @input.scan(//^/) boost = @input.scan(//d+/) raise ''missing boost value'' if boost.nil? boost.to_i else nil end end def trim_space @input.scan(//s+/) end

Tengo esta cadena:

%{Children^10 Health "sanitation management"^5}

Y quiero convertirlo para convertir esto en una matriz de valores hash:

[{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}]

Conozco StringScanner y la gema de sintaxis pero no puedo encontrar suficientes ejemplos de código para ambos.

¿Alguna sugerencia?


Lo que tienes aquí es una gramática arbitraria, y para analizarlo, lo que realmente quieres es un lexer: puedes escribir un archivo de gramática que describa tu sintaxis y luego usar el lexer para generar un analizador recursivo a partir de tu gramática.

Escribir un lexer (o incluso un analizador recursivo) no es realmente trivial, aunque es un ejercicio útil de programación, pero puede encontrar una lista de lexers / analizadores de Ruby en este mensaje de correo electrónico aquí: http://newsgroups.derkeiler.com /Archive/Comp/comp.lang.ruby/2005-11/msg02233.html

RACC está disponible como un módulo estándar de Ruby 1.8, por lo que le sugiero que se concentre en eso, incluso si su manual no es realmente fácil de seguir y requiere familiaridad con yacc.


Para un lenguaje real, un lexer es el camino a seguir, como dijo Guss . Pero si el lenguaje completo es tan complicado como su ejemplo, puede usar este truco rápido:

irb> text = %{Children^10 Health "sanitation management"^5} irb> text.scan(/(?:(/w+)|"((?://.|[^//"])*)")(?:/^(/d+))?/).map do |word,phrase,boost| { :keywords => (word || phrase).downcase, :boost => (boost.nil? ? nil : boost.to_i) } end #=> [{:boost=>10, :keywords=>"children"}, {:boost=>nil, :keywords=>"health"}, {:boost=>5, :keywords=>"sanitation management"}]

Si está tratando de analizar un idioma normal, este método será suficiente, aunque no tomaría muchas más complicaciones para que el idioma no sea regular.

Un rápido desglose de la expresión regular:

  • /w+ coincide con palabras clave de un solo término
  • (?://.|[^//"]])* usa paréntesis que no se capturan ( (?:...) ) para hacer coincidir el contenido de una cadena de texto doble escapada, ya sea un símbolo de escape ( /n , /" , // , etc.) o cualquier carácter que no sea un símbolo de escape o una cita final.
  • "((?://.|[^//"]])*)" captura solo el contenido de una frase de palabras clave entre comillas.
  • (?:(/w+)|"((?://.|[^//"])*)") coincide con cualquier palabra clave - término o frase individual, capturando términos individuales en $1 y contenidos de frases en $2
  • /d+ coincide con un número.
  • /^(/d+) captura un número siguiendo un símbolo de intercalación ( ^ ). Como este es el tercer conjunto de paréntesis de captura, se convertirá en $3 .
  • (?:/^(/d+))? captura un número siguiendo un símbolo de intercalación si está allí, coincide con la cadena vacía de lo contrario.

String#scan(regex) coincide con la expresión regular contra la cadena tantas veces como sea posible, generando una matriz de "coincidencias". Si la expresión regular contiene capturar parens, una "coincidencia" es una matriz de elementos capturados, por lo que $1 convierte en match[0] , $2 convierte en match[1] , etc. Cualquier paréntesis de captura que no match[1] con parte de los mapas de cadena a una entrada nil en el "partido" resultante.

El #map luego toma estas coincidencias, utiliza un poco de magia de bloques para dividir cada término capturado en diferentes variables (podríamos haber hecho do |match| ; word,phrase,boost = *match ), y luego crea los hashes deseados. Exactamente uno de word o phrase será nil , ya que ambos no se pueden comparar con la entrada, por lo que (word || phrase) devolverá el no- nil , y #downcase lo convertirá a minúsculas. boost.to_i convertirá una cadena en un entero mientras (boost.nil? ? nil : boost.to_i) asegurará que los nil boosts permanezcan nil .