Frage

Ich habe diese Zeichenfolge:

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

Und ich will es wandeln diese in ein Array von Hashes tokenize:

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

Ich bin mir dessen bewusst StringScanner und der Syntax gem aber ich kann nicht genug Codebeispiele für beide finden .

Alle Zeiger?

War es hilfreich?

Lösung

Für eine echte Sprache, ein Lexer ist der Weg zu gehen - wie Guss sagte . Aber wenn die volle Sprache nur als als Beispiel kompliziert, können Sie diesen schnellen Hack verwenden:

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

Wenn Sie versuchen, eine reguläre Sprache zu analysieren, dann wird diese Methode genügen -. Wenn es nicht viel mehr Komplikationen dauern würde, die Sprache nicht regulär machen

Ein schneller Abbau der Regex:

  • \w+ für ein einzelnes Zeit Keywords
  • (?:\\.|[^\\"]])* verwendet nicht-einfangenden Klammern ((?:...)) den Inhalt eines entkam doppelten Anführungszeichen Zeichenfolge übereinstimmen - entweder ein entflohener Symbol (\n, \", \\, etc.) oder ein beliebiges Zeichen, das nicht ein Fluchtsymbol oder ein Zitat Ende ist .
  • "((?:\\.|[^\\"]])*)" erfasst nur den Inhalt eines angegebenen Keyword-Satz.
  • (?:(\w+)|"((?:\\.|[^\\"])*)") paßt zu jedem Stichwort - Einzel Begriff oder Satz, einzelne Begriffe in $1 und Satzinhalt in $2 Erfassung
  • \d+ entspricht einer Zahl.
  • \^(\d+) fängt eine Reihe nach einer Caretzeichen (^). Da dies der dritte Satz von einfangenden Klammern ist, wird es caputred in $3 werden.
  • (?:\^(\d+))? fängt eine Reihe nach einem caret, wenn es da ist, den leeren String anders.

String#scan(regex) entspricht den regulären Ausdruck gegen die Saite so oft wie möglich, eine Reihe von „Treffern“ outputing. Wenn die Regex Erfassung Pars enthält, ein „Spiel“ ist ein Array von Elementen erfasst - so $1 wird match[0] wird $2 match[1] usw. Jede Capturing Klammer, die nicht gegen einen Teil der Zeichenfolge Karten zu einem nil Eintrag angepasst bekommt in der resultierende "Übereinstimmung".

Die #map nimmt dann diese Spiele, verwendet einige Block Magie jeden gefangen Begriff in verschiedene Variablen zu brechen (wir do |match| ; word,phrase,boost = *match getan haben könnte), und dann die gewünschte Hashes erzeugt. Genau ein von word oder phrase wird nil, da beide nicht gegenüber dem Eingang angepasst werden können, so (word || phrase) den Nicht-nil eines zurückkehren wird, und #downcase wird es für alle Kleinen konvertieren. boost.to_i wird eine Zeichenfolge in eine ganze Zahl konvertieren, während (boost.nil? ? nil : boost.to_i) dass nil steigert bleiben nil gewährleisten.

Andere Tipps

Hier ist ein nicht-robustes Beispiel mit StringScanner. Dies ist Code, den ich von nur angepasst Ruby-Quiz:. Parsing JSON , die eine ausgezeichnete Erklärung hat

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

Was Sie hier haben, ist eine willkürliche Grammatik, und es zu analysieren, was Sie wirklich ein Lexer wollen - Sie eine Grammatikdatei schreiben können, die Ihre Syntax beschrieben und dann die Lexer verwenden, um eine rekursive Parser von Grammatik zu erzeugen.

Schreiben einen Lexer (oder sogar einen rekursive Parser) ist nicht wirklich trivial - obwohl es eine nützliche Übung in der Programmierung ist - aber Sie können eine Liste von Ruby lexers / Parser in dieser E-Mail-Nachricht finden Sie hier: http://newsgroups.derkeiler.com/Archive/Comp/comp. lang.ruby / 2005-11 / msg02233.html

RACC ist als Standardmodul von Ruby verfügbar 1,8, so empfehle ich Ihnen darauf konzentrieren, auch wenn seine manuelle folgen nicht wirklich einfach ist und es erfordert Vertrautheit mit yacc.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top