سؤال

لدي هذه السلسلة:

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

وأريد تحويله إلى اجتياز هذا في صفيف من التجزئة:

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

أنا أدرك stringscanner و جنة بناء جملة ولكن لا يمكنني العثور على أمثلة كافية من التعليمات البرمجية لكليهما.

أي مؤشرات؟

هل كانت مفيدة؟

المحلول

للحصول على لغة حقيقية، لعبة Lexer للذهاب - مثل جوس قال. وبعد ولكن إذا كانت اللغة الكاملة معقدة مثل مثالك، فيمكنك استخدام هذا الاختراق السريع:

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

إذا كنت تحاول تحليل لغة منتظمة، فسوف تكفي هذه الطريقة - على الرغم من أنه لن يستغرق الكثير من المضاعفات لجعل اللغة غير منتظمة.

انهيار سريع لريسكس:

  • \w+ يطابق أي كلمات رئيسية واحدة
  • (?:\\.|[^\\"]])* يستخدم الأقواس غير القبض ((?:...)) مطابقة محتويات سلسلة مزدوجة هرب - إما رمز هارب (\n, \", \\, ، إلخ.) أو أي حرف واحد ليس رمز الهروب أو اقتباس نهاية.
  • "((?:\\.|[^\\"]])*)" يلتقط فقط محتويات عبارة الكلمات الرئيسية المعروضة.
  • (?:(\w+)|"((?:\\.|[^\\"])*)") يطابق أي كلمة رئيسية - مصطلح واحد أو عبارة، والتقاط بشروط واحدة $1 ومحتويات العبارة في $2
  • \d+ يطابق عدد.
  • \^(\d+) يلتقط رقما يتبع كاريت (^). لأن هذه هي المجموعة الثالثة من التقاط الأقواس، فسيكون ذلك $3.
  • (?:\^(\d+))? يلتقط الرقم التالي لقضاء سيارة إذا كان هناك، يطابق السلسلة الفارغة خلاف ذلك.

String#scan(regex) يطابق Regex مقابل السلسلة عدة مرات قدر الإمكان، إخراج مجموعة من "التطابقات". إذا كان Regex يحتوي على جنيات التقاط، فإن "المباراة" هي صفيف من العناصر التي تم التقاطها - هكذا $1 يصبح match[0], $2 يصبح match[1], ، إلخ. أي التقاط الأقواس التي لا تحصل على مطابقة ضد جزء من خرائط السلسلة إلى nil الدخول في "المباراة" الناتجة.

ال #map ثم يأخذ هذه المباريات، ويستخدم بعض السحر الكتلة لكسر كل مصطلح تم التقاطه في متغيرات مختلفة (يمكننا القيام به do |match| ; word,phrase,boost = *match)، ثم يخلق الخلاص المرغوب الخاص بك. بالضبط واحد من word أو phrase سوف يكون nil, ، بما أن كلاهما لا يمكن أن يتنابق مع المدخلات، لذلك (word || phrase) سوف تعيد غيرnil واحد و #downcase سوف يحوله إلى جميع الأحرف الصغيرة. boost.to_i سوف تحويل سلسلة إلى عدد صحيح في حين (boost.nil? ? nil : boost.to_i) سوف تضمن أن nil يعزز البقاء nil.

نصائح أخرى

هنا مثال غير قوي باستخدام StringScanner. وبعد هذا هو رمز أنا فقط تكييفها من مسابقة روبي: تحليل JSON, ، والتي لديها تفسير ممتاز.

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

ما لديك هنا هو القواعد التعسفي، وتحليلها ما تريده حقا هو Lexer - يمكنك كتابة ملف قواعد اللغة والذي وصف بناء جملة الخاص بك ثم استخدم Lexer لتوليد محلل متكرر من قواعد اللغة الخاصة بك.

كتابة Lexer (أو حتى محللا متكررا) ليس تافهة حقا - على الرغم من أنه تمرين مفيد في البرمجة - ولكن يمكنك العثور على قائمة ب Luby Lexers / المحللين في رسالة البريد الإلكتروني هذه هنا: http://newsgroups.derkeiler.com/archive/comp/comp.lang.ruby/2005-11/msg02233.html.

تتوفر RACC كوحدة قياسية ل Ruby 1.8، لذلك أقترح عليك التركيز على ذلك حتى لو كان دليله ليس من السهل اتباعه ويتطلب معرفة YACC.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top