Pregunta

Tengo esta cadena:

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

Y quiero convertirlo en tokenize esto en una serie de valores hash:

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

Soy consciente de StringScanner y la Sintaxis joya pero no puedo encontrar ejemplos de código suficiente para los dos .

Cualquier punteros?

¿Fue útil?

Solución

En un lenguaje real, un analizador léxico es el camino a seguir - como Guss dijo . Pero si el lenguaje completo solamente es tan complicado como su ejemplo, puede utilizar 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 usted está tratando de analizar un lenguaje regular entonces este método será suficiente -. Pesar de que no tomaría muchos más complicaciones para hacer que el lenguaje no regular

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

  • \w+ coincide con las palabras clave de un solo plazo
  • utiliza (?:\\.|[^\\"]])* no capturar paréntesis ((?:...)) para que coincida con el contenido de una cadena entre comillas dobles escapado - ya sea un símbolo escapado (\n, \", \\, etc.) o con cualquier carácter que no es un símbolo de escape o un fin de la cita .
  • "((?:\\.|[^\\"]])*)" capta sólo el contenido de una frase clave citado.
  • (?:(\w+)|"((?:\\.|[^\\"])*)") coincide con cualquier palabra clave - un término o frase, capturando términos individuales en contenidos y $1 frase en $2
  • \d+ coincide con un número.
  • \^(\d+) captura un número que sigue a un símbolo de intercalación (^). Dado que este es el tercer juego de la captura de paréntesis, se caputred en $3.
  • (?:\^(\d+))? captura un número que sigue a un símbolo de intercalación si está allí, coincide con la cadena vacía de otra manera.

String#scan(regex) coincide con la expresión regular con la cadena tantas veces como sea posible, outputing una serie de "coincidencias". Si la expresión regular contiene parens captura, una "coincidencia" es una serie de artículos capturado - por lo que se convierte en $1 match[0], $2 convierte match[1], etc. Cualquier paréntesis de captura que no consigue compara con parte de los mapas de cadena a una entrada en el nil resultante "partido".

El #map toma entonces estos partidos, utiliza un poco de magia para romper el bloque cada término capturado en distintas variables (que podríamos haber hecho do |match| ; word,phrase,boost = *match), y luego crea sus hashes deseados. Exactamente una de word o phrase se nil, ya que ambos no puede ser igualada en contra de la entrada, por lo (word || phrase) devolverá el no nil uno, y #downcase lo convertirá a minúsculas. boost.to_i será convertir una cadena a un entero, mientras que (boost.nil? ? nil : boost.to_i) asegurará que aumenta nil permanecen nil.

Otros consejos

Aquí está un ejemplo no robusto utilizando StringScanner. Este es el código acabo Adaptado de Rubí Cuestionario:. Analizar 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

Lo que tenemos aquí es una gramática arbitraria, y para analizarlo lo que realmente quiere es un analizador léxico - puede escribir un archivo de gramática que describe la sintaxis y luego utilizar el analizador léxico para generar un analizador recursivo de su gramática.

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

RACC está disponible como un módulo estándar de Rubí 1.8, por lo que sugiere concentrarse en que, incluso si su manual no es muy fácil de seguir y requiere familiaridad con yacc.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top