Wie tokenize ich diese Zeichenfolge in Ruby?
-
23-08-2019 - |
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?
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.